[{"data":1,"prerenderedAt":2697},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table":2668,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table":2676,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table":2681,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table":2682},{"id":4,"title":5,"body":6,"dateModified":2633,"datePublished":2633,"description":2634,"extension":2635,"faq":2636,"keyword":2645,"kind":2646,"meta":2647,"navigation":429,"path":2657,"seo":2658,"slug":2661,"stem":2662,"topics":2663,"type":2666,"__hash__":2667},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table\u002Findex.md","Add Subtotals and Grand Totals to a pandas Pivot Table",{"type":7,"value":8,"toc":2620},"minimark",[9,24,365,370,398,401,669,673,766,780,784,791,1137,1150,1312,1316,1319,1468,1475,1479,1482,1742,1750,1754,1765,2183,2193,2299,2303,2306,2490,2493,2497,2543,2547,2555,2559,2565,2571,2577,2583,2587,2616],[10,11,12,13,17,18,23],"p",{},"Excel's pivot tables show a subtotal under every region and a grand total at the bottom, and managers expect the same from a report generated in Python. pandas gets you halfway: ",[14,15,16],"code",{},"pivot_table(..., margins=True)"," adds a grand total, but a two-level pivot — region and city — gets no region subtotals. This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data",", adds them properly: computed from the raw data so averages are right, placed after each group, and written to Excel either as formatted values or as live formulas.",[25,26,34,35,34,39,34,43,34,51,34,60,34,64,34,71,34,75,34,79,34,83,34,87,34,91,34,95,34,99,34,103,34,107,34,112,34,114,34,118,34,120,34,123,34,125,34,128,34,130,34,133,34,135,34,138,34,141,34,145,34,147,34,151,34,153,34,156,34,158,34,161,34,163,34,166,34,168,34,171,34,174,34,178,34,180,34,182,34,184,34,187,34,189,34,192,34,194,34,197,34,199,34,202,34,205,34,209,34,214,34,217,34,219,34,222,34,224,34,227,34,229,34,232,34,234,34,237,34,240,34,244,34,246,34,249,34,251,34,254,34,256,34,258,34,260,34,263,34,265,34,268,34,271,34,275,34,277,34,279,34,281,34,284,34,286,34,289,34,291,34,293,34,295,34,298,34,301,34,305,34,307,34,309,34,311,34,313,34,315,34,318,34,320,34,323,34,325,34,328,34,331,34,335,34,339,34,342,34,344,34,346,34,349,34,351,34,354,34,356,34,359],"svg",{"viewBox":27,"role":28,"ariaLabelledBy":29,"xmlns":32,"style":33},"152 0 455 306","img",[30,31],"st-target-t","st-target-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:455px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[36,37,38],"title",{"id":30},"The layout readers expect from a pivot report",[40,41,42],"desc",{"id":31},"A pivot of revenue by region and city with quarters across. Leeds and York are followed by a North subtotal row, Bath and Exeter by a South subtotal row, and a grand total row closes the table. Subtotal and total rows are highlighted.",[44,45],"rect",{"x":46,"y":47,"width":48,"height":49,"fill":50},"152","0","455","306","#ffffff",[44,52],{"x":53,"y":54,"width":55,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"216.2","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[44,61],{"x":62,"y":54,"width":63,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"248.2","66.7",[65,66,70],"text",{"x":67,"y":68,"style":69},"281.6","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[44,72],{"x":73,"y":54,"width":74,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"314.9","75.7",[65,76,78],{"x":77,"y":68,"style":69},"352.8","B",[44,80],{"x":81,"y":54,"width":82,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"390.6","49.2",[65,84,86],{"x":85,"y":68,"style":69},"415.2","C",[44,88],{"x":89,"y":54,"width":90,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"439.8","48.5",[65,92,94],{"x":93,"y":68,"style":69},"464.1","D",[44,96],{"x":97,"y":54,"width":98,"height":56,"rx":47,"fill":57,"stroke":58,"style":59},"488.3","55.5",[65,100,102],{"x":101,"y":68,"style":69},"516.1","E",[44,104],{"x":53,"y":105,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"40.0","28.0",[65,108,111],{"x":109,"y":110,"style":69},"232.2","59.0","1",[44,113],{"x":62,"y":105,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,115,117],{"x":67,"y":110,"style":116},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Region",[44,119],{"x":73,"y":105,"width":74,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,121,122],{"x":77,"y":110,"style":116},"City",[44,124],{"x":81,"y":105,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,126,127],{"x":85,"y":110,"style":116},"Q1",[44,129],{"x":89,"y":105,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,131,132],{"x":93,"y":110,"style":116},"Q2",[44,134],{"x":97,"y":105,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,136,137],{"x":101,"y":110,"style":116},"Total",[44,139],{"x":53,"y":140,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"68.0",[65,142,144],{"x":109,"y":143,"style":69},"87.0","2",[44,146],{"x":62,"y":140,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,148,150],{"x":67,"y":143,"style":149},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[44,152],{"x":73,"y":140,"width":74,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,154,155],{"x":77,"y":143,"style":149},"Leeds",[44,157],{"x":81,"y":140,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,159,160],{"x":85,"y":143,"style":149},"120",[44,162],{"x":89,"y":140,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,164,165],{"x":93,"y":143,"style":149},"150",[44,167],{"x":97,"y":140,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,169,170],{"x":101,"y":143,"style":149},"270",[44,172],{"x":53,"y":173,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"96.0",[65,175,177],{"x":109,"y":176,"style":69},"115.0","3",[44,179],{"x":62,"y":173,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,181,150],{"x":67,"y":176,"style":149},[44,183],{"x":73,"y":173,"width":74,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,185,186],{"x":77,"y":176,"style":149},"York",[44,188],{"x":81,"y":173,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,190,191],{"x":85,"y":176,"style":149},"80",[44,193],{"x":89,"y":173,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,195,196],{"x":93,"y":176,"style":149},"100",[44,198],{"x":97,"y":173,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,200,201],{"x":101,"y":176,"style":149},"180",[44,203],{"x":53,"y":204,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"124.0",[65,206,208],{"x":109,"y":207,"style":69},"143.0","4",[44,210],{"x":62,"y":204,"width":63,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[65,215,150],{"x":67,"y":207,"style":216},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[44,218],{"x":73,"y":204,"width":74,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,220,221],{"x":77,"y":207,"style":216},"Subtotal",[44,223],{"x":81,"y":204,"width":82,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,225,226],{"x":85,"y":207,"style":216},"200",[44,228],{"x":89,"y":204,"width":90,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,230,231],{"x":93,"y":207,"style":216},"250",[44,233],{"x":97,"y":204,"width":98,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,235,236],{"x":101,"y":207,"style":216},"450",[44,238],{"x":53,"y":239,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"152.0",[65,241,243],{"x":109,"y":242,"style":69},"171.0","5",[44,245],{"x":62,"y":239,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,247,248],{"x":67,"y":242,"style":149},"South",[44,250],{"x":73,"y":239,"width":74,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,252,253],{"x":77,"y":242,"style":149},"Bath",[44,255],{"x":81,"y":239,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,257,226],{"x":85,"y":242,"style":149},[44,259],{"x":89,"y":239,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,261,262],{"x":93,"y":242,"style":149},"210",[44,264],{"x":97,"y":239,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,266,267],{"x":101,"y":242,"style":149},"410",[44,269],{"x":53,"y":270,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"180.0",[65,272,274],{"x":109,"y":273,"style":69},"199.0","6",[44,276],{"x":62,"y":270,"width":63,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,278,248],{"x":67,"y":273,"style":149},[44,280],{"x":73,"y":270,"width":74,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,282,283],{"x":77,"y":273,"style":149},"Exeter",[44,285],{"x":81,"y":270,"width":82,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,287,288],{"x":85,"y":273,"style":149},"90",[44,290],{"x":89,"y":270,"width":90,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,292,196],{"x":93,"y":273,"style":149},[44,294],{"x":97,"y":270,"width":98,"height":106,"rx":47,"fill":50,"stroke":58,"style":59},[65,296,297],{"x":101,"y":273,"style":149},"190",[44,299],{"x":53,"y":300,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"208.0",[65,302,304],{"x":109,"y":303,"style":69},"227.0","7",[44,306],{"x":62,"y":300,"width":63,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,308,248],{"x":67,"y":303,"style":216},[44,310],{"x":73,"y":300,"width":74,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,312,221],{"x":77,"y":303,"style":216},[44,314],{"x":81,"y":300,"width":82,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,316,317],{"x":85,"y":303,"style":216},"290",[44,319],{"x":89,"y":300,"width":90,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,321,322],{"x":93,"y":303,"style":216},"310",[44,324],{"x":97,"y":300,"width":98,"height":106,"rx":47,"fill":211,"stroke":212,"style":213},[65,326,327],{"x":101,"y":303,"style":216},"600",[44,329],{"x":53,"y":330,"width":55,"height":106,"rx":47,"fill":57,"stroke":58,"style":59},"236.0",[65,332,334],{"x":109,"y":333,"style":69},"255.0","8",[44,336],{"x":62,"y":330,"width":63,"height":106,"rx":47,"fill":337,"stroke":338,"style":213},"#f0f4ff","var(--brand,#5b5cf0)",[65,340,137],{"x":67,"y":333,"style":341},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[44,343],{"x":73,"y":330,"width":74,"height":106,"rx":47,"fill":337,"stroke":338,"style":213},[44,345],{"x":81,"y":330,"width":82,"height":106,"rx":47,"fill":337,"stroke":338,"style":213},[65,347,348],{"x":85,"y":333,"style":341},"490",[44,350],{"x":89,"y":330,"width":90,"height":106,"rx":47,"fill":337,"stroke":338,"style":213},[65,352,353],{"x":93,"y":333,"style":341},"560",[44,355],{"x":97,"y":330,"width":98,"height":106,"rx":47,"fill":337,"stroke":338,"style":213},[65,357,358],{"x":101,"y":333,"style":341},"1050",[65,360,364],{"x":361,"y":362,"style":363},"380.0","286.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","margins=True gives only the last row; the teal rows need extra work",[366,367,369],"h2",{"id":368},"prerequisites","Prerequisites",[371,372,377],"pre",{"className":373,"code":374,"language":375,"meta":376,"style":376},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,378,379],{"__ignoreMap":376},[380,381,384,388,392,395],"span",{"class":382,"line":383},"line",1,[380,385,387],{"class":386},"sMTad","pip",[380,389,391],{"class":390},"srMev"," install",[380,393,394],{"class":390}," pandas",[380,396,397],{"class":390}," openpyxl\n",[10,399,400],{},"Sample sales data, as it would come from an export sheet:",[371,402,406],{"className":403,"code":404,"language":405,"meta":376,"style":376},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\n    \"Region\": [\"North\"] * 5 + [\"South\"] * 4,\n    \"City\": [\"Leeds\", \"Leeds\", \"York\", \"York\", \"York\", \"Bath\", \"Bath\", \"Exeter\", \"Exeter\"],\n    \"Quarter\": [\"Q1\", \"Q2\", \"Q1\", \"Q2\", \"Q2\", \"Q1\", \"Q2\", \"Q1\", \"Q2\"],\n    \"Revenue\": [120, 150, 80, 60, 40, 200, 210, 90, 100],\n})\nsales.to_excel(\"sales.xlsx\", index=False)\nsales = pd.read_excel(\"sales.xlsx\")\n","python",[14,407,408,424,431,443,484,534,580,626,632,655],{"__ignoreMap":376},[380,409,410,414,418,421],{"class":382,"line":383},[380,411,413],{"class":412},"s-kum","import",[380,415,417],{"class":416},"skGVy"," pandas ",[380,419,420],{"class":412},"as",[380,422,423],{"class":416}," pd\n",[380,425,427],{"class":382,"line":426},2,[380,428,430],{"emptyLinePlaceholder":429},true,"\n",[380,432,434,437,440],{"class":382,"line":433},3,[380,435,436],{"class":416},"sales ",[380,438,439],{"class":412},"=",[380,441,442],{"class":416}," pd.DataFrame({\n",[380,444,446,449,452,455,458,461,465,468,471,474,476,478,481],{"class":382,"line":445},4,[380,447,448],{"class":390},"    \"Region\"",[380,450,451],{"class":416},": [",[380,453,454],{"class":390},"\"North\"",[380,456,457],{"class":416},"] ",[380,459,460],{"class":412},"*",[380,462,464],{"class":463},"sP0c6"," 5",[380,466,467],{"class":412}," +",[380,469,470],{"class":416}," [",[380,472,473],{"class":390},"\"South\"",[380,475,457],{"class":416},[380,477,460],{"class":412},[380,479,480],{"class":463}," 4",[380,482,483],{"class":416},",\n",[380,485,487,490,492,495,498,500,502,505,507,509,511,513,515,518,520,522,524,527,529,531],{"class":382,"line":486},5,[380,488,489],{"class":390},"    \"City\"",[380,491,451],{"class":416},[380,493,494],{"class":390},"\"Leeds\"",[380,496,497],{"class":416},", ",[380,499,494],{"class":390},[380,501,497],{"class":416},[380,503,504],{"class":390},"\"York\"",[380,506,497],{"class":416},[380,508,504],{"class":390},[380,510,497],{"class":416},[380,512,504],{"class":390},[380,514,497],{"class":416},[380,516,517],{"class":390},"\"Bath\"",[380,519,497],{"class":416},[380,521,517],{"class":390},[380,523,497],{"class":416},[380,525,526],{"class":390},"\"Exeter\"",[380,528,497],{"class":416},[380,530,526],{"class":390},[380,532,533],{"class":416},"],\n",[380,535,537,540,542,545,547,550,552,554,556,558,560,562,564,566,568,570,572,574,576,578],{"class":382,"line":536},6,[380,538,539],{"class":390},"    \"Quarter\"",[380,541,451],{"class":416},[380,543,544],{"class":390},"\"Q1\"",[380,546,497],{"class":416},[380,548,549],{"class":390},"\"Q2\"",[380,551,497],{"class":416},[380,553,544],{"class":390},[380,555,497],{"class":416},[380,557,549],{"class":390},[380,559,497],{"class":416},[380,561,549],{"class":390},[380,563,497],{"class":416},[380,565,544],{"class":390},[380,567,497],{"class":416},[380,569,549],{"class":390},[380,571,497],{"class":416},[380,573,544],{"class":390},[380,575,497],{"class":416},[380,577,549],{"class":390},[380,579,533],{"class":416},[380,581,583,586,588,590,592,594,596,598,600,603,605,608,610,612,614,616,618,620,622,624],{"class":382,"line":582},7,[380,584,585],{"class":390},"    \"Revenue\"",[380,587,451],{"class":416},[380,589,160],{"class":463},[380,591,497],{"class":416},[380,593,165],{"class":463},[380,595,497],{"class":416},[380,597,191],{"class":463},[380,599,497],{"class":416},[380,601,602],{"class":463},"60",[380,604,497],{"class":416},[380,606,607],{"class":463},"40",[380,609,497],{"class":416},[380,611,226],{"class":463},[380,613,497],{"class":416},[380,615,262],{"class":463},[380,617,497],{"class":416},[380,619,288],{"class":463},[380,621,497],{"class":416},[380,623,196],{"class":463},[380,625,533],{"class":416},[380,627,629],{"class":382,"line":628},8,[380,630,631],{"class":416},"})\n",[380,633,635,638,641,643,647,649,652],{"class":382,"line":634},9,[380,636,637],{"class":416},"sales.to_excel(",[380,639,640],{"class":390},"\"sales.xlsx\"",[380,642,497],{"class":416},[380,644,646],{"class":645},"sa561","index",[380,648,439],{"class":412},[380,650,651],{"class":463},"False",[380,653,654],{"class":416},")\n",[380,656,658,660,662,665,667],{"class":382,"line":657},10,[380,659,436],{"class":416},[380,661,439],{"class":412},[380,663,664],{"class":416}," pd.read_excel(",[380,666,640],{"class":390},[380,668,654],{"class":416},[366,670,672],{"id":671},"step-1-the-grand-total-with-margins","Step 1 — The grand total with margins",[371,674,676],{"className":403,"code":675,"language":405,"meta":376,"style":376},"pt = sales.pivot_table(index=[\"Region\", \"City\"], columns=\"Quarter\", values=\"Revenue\",\n                       aggfunc=\"sum\", margins=True, margins_name=\"Total\")\nprint(pt)\n",[14,677,678,726,758],{"__ignoreMap":376},[380,679,680,683,685,688,690,692,695,698,700,703,706,709,711,714,716,719,721,724],{"class":382,"line":383},[380,681,682],{"class":416},"pt ",[380,684,439],{"class":412},[380,686,687],{"class":416}," sales.pivot_table(",[380,689,646],{"class":645},[380,691,439],{"class":412},[380,693,694],{"class":416},"[",[380,696,697],{"class":390},"\"Region\"",[380,699,497],{"class":416},[380,701,702],{"class":390},"\"City\"",[380,704,705],{"class":416},"], ",[380,707,708],{"class":645},"columns",[380,710,439],{"class":412},[380,712,713],{"class":390},"\"Quarter\"",[380,715,497],{"class":416},[380,717,718],{"class":645},"values",[380,720,439],{"class":412},[380,722,723],{"class":390},"\"Revenue\"",[380,725,483],{"class":416},[380,727,728,731,733,736,738,741,743,746,748,751,753,756],{"class":382,"line":426},[380,729,730],{"class":645},"                       aggfunc",[380,732,439],{"class":412},[380,734,735],{"class":390},"\"sum\"",[380,737,497],{"class":416},[380,739,740],{"class":645},"margins",[380,742,439],{"class":412},[380,744,745],{"class":463},"True",[380,747,497],{"class":416},[380,749,750],{"class":645},"margins_name",[380,752,439],{"class":412},[380,754,755],{"class":390},"\"Total\"",[380,757,654],{"class":416},[380,759,760,763],{"class":382,"line":433},[380,761,762],{"class":463},"print",[380,764,765],{"class":416},"(pt)\n",[10,767,768,769,771,772,774,775,779],{},"The result has a ",[14,770,137],{}," column and a final ",[14,773,137],{}," row — 1,050 — computed from all the raw rows. There is nothing between Leeds, York and Bath to show where North ends. Grand totals and their formatting are covered in ",[19,776,778],{"href":777},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted\u002F","Export a pandas Pivot Table to Excel, Formatted","; the rest of this guide is about the rows in between.",[366,781,783],{"id":782},"step-2-build-subtotal-rows-from-the-raw-data","Step 2 — Build subtotal rows from the raw data",[10,785,786,787,790],{},"A subtotal is just the same pivot computed one level up. Build it with a second ",[14,788,789],{},"pivot_table"," on the outer level only, then give it the same two-level index as the detail rows, with \"Subtotal\" in the inner position:",[371,792,794],{"className":403,"code":793,"language":405,"meta":376,"style":376},"def with_subtotals(data, rows, columns, values, aggfunc=\"sum\", label=\"Subtotal\", total=\"Total\"):\n    detail = data.pivot_table(index=rows, columns=columns, values=values,\n                              aggfunc=aggfunc, margins=True, margins_name=total)\n    grand = detail.loc[[total]]\n    detail = detail.drop(index=total, level=0)\n    sub = data.pivot_table(index=rows[0], columns=columns, values=values,\n                           aggfunc=aggfunc, margins=True, margins_name=total)\n    sub = sub.drop(index=total)\n    sub.index = pd.MultiIndex.from_arrays([sub.index, [label] * len(sub)], names=rows)\n    grand.index = pd.MultiIndex.from_tuples([(total, \"\")], names=rows)\n    parts = []\n    for group in detail.index.get_level_values(0).unique():\n        parts += [detail.loc[[group]], sub.loc[[group]]]\n    return pd.concat(parts + [grand])\n\nreport = with_subtotals(sales, [\"Region\", \"City\"], \"Quarter\", \"Revenue\")\nprint(report)\n",[14,795,796,830,861,886,896,921,953,976,991,1017,1039,1050,1070,1082,1097,1102,1129],{"__ignoreMap":376},[380,797,798,801,805,808,810,812,815,817,820,823,825,827],{"class":382,"line":383},[380,799,800],{"class":412},"def",[380,802,804],{"class":803},"s_Opv"," with_subtotals",[380,806,807],{"class":416},"(data, rows, columns, values, aggfunc",[380,809,439],{"class":412},[380,811,735],{"class":390},[380,813,814],{"class":416},", label",[380,816,439],{"class":412},[380,818,819],{"class":390},"\"Subtotal\"",[380,821,822],{"class":416},", total",[380,824,439],{"class":412},[380,826,755],{"class":390},[380,828,829],{"class":416},"):\n",[380,831,832,835,837,840,842,844,847,849,851,854,856,858],{"class":382,"line":426},[380,833,834],{"class":416},"    detail ",[380,836,439],{"class":412},[380,838,839],{"class":416}," data.pivot_table(",[380,841,646],{"class":645},[380,843,439],{"class":412},[380,845,846],{"class":416},"rows, ",[380,848,708],{"class":645},[380,850,439],{"class":412},[380,852,853],{"class":416},"columns, ",[380,855,718],{"class":645},[380,857,439],{"class":412},[380,859,860],{"class":416},"values,\n",[380,862,863,866,868,871,873,875,877,879,881,883],{"class":382,"line":433},[380,864,865],{"class":645},"                              aggfunc",[380,867,439],{"class":412},[380,869,870],{"class":416},"aggfunc, ",[380,872,740],{"class":645},[380,874,439],{"class":412},[380,876,745],{"class":463},[380,878,497],{"class":416},[380,880,750],{"class":645},[380,882,439],{"class":412},[380,884,885],{"class":416},"total)\n",[380,887,888,891,893],{"class":382,"line":445},[380,889,890],{"class":416},"    grand ",[380,892,439],{"class":412},[380,894,895],{"class":416}," detail.loc[[total]]\n",[380,897,898,900,902,905,907,909,912,915,917,919],{"class":382,"line":486},[380,899,834],{"class":416},[380,901,439],{"class":412},[380,903,904],{"class":416}," detail.drop(",[380,906,646],{"class":645},[380,908,439],{"class":412},[380,910,911],{"class":416},"total, ",[380,913,914],{"class":645},"level",[380,916,439],{"class":412},[380,918,47],{"class":463},[380,920,654],{"class":416},[380,922,923,926,928,930,932,934,937,939,941,943,945,947,949,951],{"class":382,"line":536},[380,924,925],{"class":416},"    sub ",[380,927,439],{"class":412},[380,929,839],{"class":416},[380,931,646],{"class":645},[380,933,439],{"class":412},[380,935,936],{"class":416},"rows[",[380,938,47],{"class":463},[380,940,705],{"class":416},[380,942,708],{"class":645},[380,944,439],{"class":412},[380,946,853],{"class":416},[380,948,718],{"class":645},[380,950,439],{"class":412},[380,952,860],{"class":416},[380,954,955,958,960,962,964,966,968,970,972,974],{"class":382,"line":582},[380,956,957],{"class":645},"                           aggfunc",[380,959,439],{"class":412},[380,961,870],{"class":416},[380,963,740],{"class":645},[380,965,439],{"class":412},[380,967,745],{"class":463},[380,969,497],{"class":416},[380,971,750],{"class":645},[380,973,439],{"class":412},[380,975,885],{"class":416},[380,977,978,980,982,985,987,989],{"class":382,"line":628},[380,979,925],{"class":416},[380,981,439],{"class":412},[380,983,984],{"class":416}," sub.drop(",[380,986,646],{"class":645},[380,988,439],{"class":412},[380,990,885],{"class":416},[380,992,993,996,998,1001,1003,1006,1009,1012,1014],{"class":382,"line":634},[380,994,995],{"class":416},"    sub.index ",[380,997,439],{"class":412},[380,999,1000],{"class":416}," pd.MultiIndex.from_arrays([sub.index, [label] ",[380,1002,460],{"class":412},[380,1004,1005],{"class":463}," len",[380,1007,1008],{"class":416},"(sub)], ",[380,1010,1011],{"class":645},"names",[380,1013,439],{"class":412},[380,1015,1016],{"class":416},"rows)\n",[380,1018,1019,1022,1024,1027,1030,1033,1035,1037],{"class":382,"line":657},[380,1020,1021],{"class":416},"    grand.index ",[380,1023,439],{"class":412},[380,1025,1026],{"class":416}," pd.MultiIndex.from_tuples([(total, ",[380,1028,1029],{"class":390},"\"\"",[380,1031,1032],{"class":416},")], ",[380,1034,1011],{"class":645},[380,1036,439],{"class":412},[380,1038,1016],{"class":416},[380,1040,1042,1045,1047],{"class":382,"line":1041},11,[380,1043,1044],{"class":416},"    parts ",[380,1046,439],{"class":412},[380,1048,1049],{"class":416}," []\n",[380,1051,1053,1056,1059,1062,1065,1067],{"class":382,"line":1052},12,[380,1054,1055],{"class":412},"    for",[380,1057,1058],{"class":416}," group ",[380,1060,1061],{"class":412},"in",[380,1063,1064],{"class":416}," detail.index.get_level_values(",[380,1066,47],{"class":463},[380,1068,1069],{"class":416},").unique():\n",[380,1071,1073,1076,1079],{"class":382,"line":1072},13,[380,1074,1075],{"class":416},"        parts ",[380,1077,1078],{"class":412},"+=",[380,1080,1081],{"class":416}," [detail.loc[[group]], sub.loc[[group]]]\n",[380,1083,1085,1088,1091,1094],{"class":382,"line":1084},14,[380,1086,1087],{"class":412},"    return",[380,1089,1090],{"class":416}," pd.concat(parts ",[380,1092,1093],{"class":412},"+",[380,1095,1096],{"class":416}," [grand])\n",[380,1098,1100],{"class":382,"line":1099},15,[380,1101,430],{"emptyLinePlaceholder":429},[380,1103,1105,1108,1110,1113,1115,1117,1119,1121,1123,1125,1127],{"class":382,"line":1104},16,[380,1106,1107],{"class":416},"report ",[380,1109,439],{"class":412},[380,1111,1112],{"class":416}," with_subtotals(sales, [",[380,1114,697],{"class":390},[380,1116,497],{"class":416},[380,1118,702],{"class":390},[380,1120,705],{"class":416},[380,1122,713],{"class":390},[380,1124,497],{"class":416},[380,1126,723],{"class":390},[380,1128,654],{"class":416},[380,1130,1132,1134],{"class":382,"line":1131},17,[380,1133,762],{"class":463},[380,1135,1136],{"class":416},"(report)\n",[10,1138,1139,1140,1143,1144,1146,1147,1149],{},"Building the output group by group keeps the regions in their original order and puts each subtotal directly after its cities. The subtotal pivot also uses ",[14,1141,1142],{},"margins=True",", so each subtotal row gets its own ",[14,1145,137],{}," column value. And because both the subtotals and the grand total come from ",[14,1148,789],{}," over the raw rows, they are correct for any aggregation — which matters as soon as the numbers are not sums.",[25,1151,34,1157,34,1160,34,1163,34,1167,34,1174,34,1185,34,1191,34,1196,34,1201,34,1206,34,1209,34,1216,34,1221,34,1224,34,1228,34,1231,34,1234,34,1240,34,1244,34,1247,34,1251,34,1255,34,1258,34,1263,34,1270,34,1276,34,1279,34,1282,34,1286,34,1290,34,1293,34,1296,34,1302,34,1306,34,1309],{"viewBox":1152,"role":28,"ariaLabelledBy":1153,"xmlns":32,"style":1156},"4 0 752 160",[1154,1155],"st-build-t","st-build-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[36,1158,1159],{"id":1154},"How the subtotal table is assembled",[40,1161,1162],{"id":1155},"A detail pivot by region and city and a second pivot by region alone are built from the same raw rows. The region pivot gets Subtotal as its inner label, then the tables are interleaved group by group and the grand total row is appended last.",[44,1164],{"x":208,"y":47,"width":1165,"height":1166,"fill":50},"752","160",[44,1168],{"x":1169,"y":54,"width":1170,"height":1171,"rx":1172,"fill":337,"stroke":338,"style":1173},"20.0","115.2","128.0","12","stroke-width:2px",[1175,1176,1178],"g",{"transform":1177},"translate(65.6,46.5) scale(1.000)",[1179,1180],"path",{"d":1181,"fill":1182,"stroke":1183,"style":1184},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[65,1186,1190],{"x":1187,"y":1188,"style":1189},"77.6","93.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Detail pivot",[65,1192,1195],{"x":1187,"y":1193,"style":1194},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Region, City",[382,1197],{"x1":1198,"y1":1199,"x2":1200,"y2":1199,"stroke":338,"style":1173},"140.2","80.0","162.2",[1202,1203],"polygon",{"points":1204,"fill":1205},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[44,1207],{"x":1208,"y":54,"width":1170,"height":1171,"rx":1172,"fill":211,"stroke":212,"style":1173},"171.2",[1175,1210,1212],{"transform":1211},"translate(216.8,46.5) scale(1.000)",[1179,1213],{"d":1214,"fill":1182,"stroke":1215,"style":1184},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--teal-ink,#0b6157)",[65,1217,1220],{"x":1218,"y":1188,"style":1219},"228.8","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Region pivot",[65,1222,1223],{"x":1218,"y":1193,"style":1194},"same raw rows",[382,1225],{"x1":1226,"y1":1199,"x2":1227,"y2":1199,"stroke":338,"style":1173},"291.4","313.4",[1202,1229],{"points":1230,"fill":1205},"317.4,80.0 308.4,85.0 308.4,75.0",[44,1232],{"x":1233,"y":54,"width":1170,"height":1171,"rx":1172,"fill":337,"stroke":338,"style":1173},"322.4",[1175,1235,1237],{"transform":1236},"translate(368.0,37.8) scale(1.000)",[1179,1238],{"d":1239,"fill":1182,"stroke":1183,"style":1184},"M5 9h14 M4 15h14 M10 4l-2 16 M16 4l-2 16",[65,1241,1243],{"x":361,"y":1242,"style":1189},"84.2","Label as",[65,1245,221],{"x":361,"y":1246,"style":1189},"101.8",[65,1248,1250],{"x":361,"y":1249,"style":1194},"121.2","two-level index",[382,1252],{"x1":1253,"y1":1199,"x2":1254,"y2":1199,"stroke":338,"style":1173},"442.6","464.6",[1202,1256],{"points":1257,"fill":1205},"468.6,80.0 459.6,85.0 459.6,75.0",[44,1259],{"x":1260,"y":54,"width":1170,"height":1171,"rx":1172,"fill":1261,"stroke":1262,"style":1173},"473.6","#fdefd8","var(--gold,#b4740a)",[1175,1264,1266],{"transform":1265},"translate(519.2,29.0) scale(1.000)",[1179,1267],{"d":1268,"fill":1182,"stroke":1269,"style":1184},"M6 4v5a5 5 0 0 0 5 5h7 M6 20v-5 M15 11l3 3-3 3","var(--gold-ink,#7a4e06)",[65,1271,1275],{"x":1272,"y":1273,"style":1274},"531.2","75.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Interleave",[65,1277,1278],{"x":1272,"y":1188,"style":1274},"groups",[65,1280,1281],{"x":1272,"y":1193,"style":1194},"detail then",[65,1283,1285],{"x":1272,"y":1284,"style":1194},"130.0","subtotal",[382,1287],{"x1":1288,"y1":1199,"x2":1289,"y2":1199,"stroke":338,"style":1173},"593.8","615.8",[1202,1291],{"points":1292,"fill":1205},"619.8,80.0 610.8,85.0 610.8,75.0",[44,1294],{"x":1295,"y":54,"width":1170,"height":1171,"rx":1172,"fill":337,"stroke":338,"style":1173},"624.8",[1175,1297,1299],{"transform":1298},"translate(670.4,37.8) scale(1.000)",[1179,1300],{"d":1301,"fill":1182,"stroke":1183,"style":1184},"M5 12.5l4.5 4.5L19 7",[65,1303,1305],{"x":1304,"y":1242,"style":1189},"682.4","Append",[65,1307,1308],{"x":1304,"y":1246,"style":1189},"grand total",[65,1310,1311],{"x":1304,"y":1249,"style":1194},"margins row",[366,1313,1315],{"id":1314},"step-3-averages-why-raw-data-matters","Step 3 — Averages: why raw data matters",[10,1317,1318],{},"Suppose the report shows average order value instead of total revenue. The tempting shortcut is to average the city rows of each group. That weights York — three orders — the same as Leeds with two:",[371,1320,1322],{"className":403,"code":1321,"language":405,"meta":376,"style":376},"city_avg = sales.pivot_table(index=[\"Region\", \"City\"], values=\"Revenue\", aggfunc=\"mean\")\nprint(city_avg.groupby(level=\"Region\").mean()[\"Revenue\"].to_dict())   # {'North': 97.5, 'South': 150.0}\nprint(sales.groupby(\"Region\")[\"Revenue\"].mean().to_dict())            # {'North': 90.0, 'South': 150.0}\n\navg_report = with_subtotals(sales, [\"Region\", \"City\"], \"Quarter\", \"Revenue\", aggfunc=\"mean\")\nprint(avg_report.loc[(\"North\", \"Subtotal\")].round(1).to_dict())\n",[14,1323,1324,1365,1390,1410,1414,1447],{"__ignoreMap":376},[380,1325,1326,1329,1331,1333,1335,1337,1339,1341,1343,1345,1347,1349,1351,1353,1355,1358,1360,1363],{"class":382,"line":383},[380,1327,1328],{"class":416},"city_avg ",[380,1330,439],{"class":412},[380,1332,687],{"class":416},[380,1334,646],{"class":645},[380,1336,439],{"class":412},[380,1338,694],{"class":416},[380,1340,697],{"class":390},[380,1342,497],{"class":416},[380,1344,702],{"class":390},[380,1346,705],{"class":416},[380,1348,718],{"class":645},[380,1350,439],{"class":412},[380,1352,723],{"class":390},[380,1354,497],{"class":416},[380,1356,1357],{"class":645},"aggfunc",[380,1359,439],{"class":412},[380,1361,1362],{"class":390},"\"mean\"",[380,1364,654],{"class":416},[380,1366,1367,1369,1372,1374,1376,1378,1381,1383,1386],{"class":382,"line":426},[380,1368,762],{"class":463},[380,1370,1371],{"class":416},"(city_avg.groupby(",[380,1373,914],{"class":645},[380,1375,439],{"class":412},[380,1377,697],{"class":390},[380,1379,1380],{"class":416},").mean()[",[380,1382,723],{"class":390},[380,1384,1385],{"class":416},"].to_dict())   ",[380,1387,1389],{"class":1388},"s-wDw","# {'North': 97.5, 'South': 150.0}\n",[380,1391,1392,1394,1397,1399,1402,1404,1407],{"class":382,"line":433},[380,1393,762],{"class":463},[380,1395,1396],{"class":416},"(sales.groupby(",[380,1398,697],{"class":390},[380,1400,1401],{"class":416},")[",[380,1403,723],{"class":390},[380,1405,1406],{"class":416},"].mean().to_dict())            ",[380,1408,1409],{"class":1388},"# {'North': 90.0, 'South': 150.0}\n",[380,1411,1412],{"class":382,"line":445},[380,1413,430],{"emptyLinePlaceholder":429},[380,1415,1416,1419,1421,1423,1425,1427,1429,1431,1433,1435,1437,1439,1441,1443,1445],{"class":382,"line":486},[380,1417,1418],{"class":416},"avg_report ",[380,1420,439],{"class":412},[380,1422,1112],{"class":416},[380,1424,697],{"class":390},[380,1426,497],{"class":416},[380,1428,702],{"class":390},[380,1430,705],{"class":416},[380,1432,713],{"class":390},[380,1434,497],{"class":416},[380,1436,723],{"class":390},[380,1438,497],{"class":416},[380,1440,1357],{"class":645},[380,1442,439],{"class":412},[380,1444,1362],{"class":390},[380,1446,654],{"class":416},[380,1448,1449,1451,1454,1456,1458,1460,1463,1465],{"class":382,"line":536},[380,1450,762],{"class":463},[380,1452,1453],{"class":416},"(avg_report.loc[(",[380,1455,454],{"class":390},[380,1457,497],{"class":416},[380,1459,819],{"class":390},[380,1461,1462],{"class":416},")].round(",[380,1464,111],{"class":463},[380,1466,1467],{"class":416},").to_dict())\n",[10,1469,1470,1471,1474],{},"North's true average is 90, not 97.5. ",[14,1472,1473],{},"with_subtotals"," gets it right because each subtotal is a fresh aggregation of the region's raw rows. The same applies to counts of distinct customers, medians and percentages: never derive a subtotal from the rows above it unless the measure is a plain sum.",[366,1476,1478],{"id":1477},"step-4-write-the-report-with-bold-subtotal-rows","Step 4 — Write the report with bold subtotal rows",[10,1480,1481],{},"Flatten the index into columns and write the table, then style every subtotal and total row so readers can find them:",[371,1483,1485],{"className":403,"code":1484,"language":405,"meta":376,"style":376},"from openpyxl import load_workbook\nfrom openpyxl.styles import Font, PatternFill\n\nflat = report.reset_index()\nflat.columns.name = None\nflat.to_excel(\"pivot_subtotals.xlsx\", index=False)\n\nwb = load_workbook(\"pivot_subtotals.xlsx\")\nws = wb.active\nbold = Font(bold=True)\nshade = PatternFill(\"solid\", fgColor=\"E7EBEF\")\nfor row in ws.iter_rows(min_row=2):\n    if row[1].value == \"Subtotal\" or row[0].value == \"Total\":\n        for cell in row:\n            cell.font = bold\n            cell.fill = shade\nwb.save(\"pivot_subtotals.xlsx\")\nprint(flat.values.tolist()[2])     # ['North', 'Subtotal', 200, 250, 450]\n",[14,1486,1487,1500,1512,1516,1526,1536,1554,1558,1572,1582,1601,1626,1648,1684,1697,1707,1717,1726],{"__ignoreMap":376},[380,1488,1489,1492,1495,1497],{"class":382,"line":383},[380,1490,1491],{"class":412},"from",[380,1493,1494],{"class":416}," openpyxl ",[380,1496,413],{"class":412},[380,1498,1499],{"class":416}," load_workbook\n",[380,1501,1502,1504,1507,1509],{"class":382,"line":426},[380,1503,1491],{"class":412},[380,1505,1506],{"class":416}," openpyxl.styles ",[380,1508,413],{"class":412},[380,1510,1511],{"class":416}," Font, PatternFill\n",[380,1513,1514],{"class":382,"line":433},[380,1515,430],{"emptyLinePlaceholder":429},[380,1517,1518,1521,1523],{"class":382,"line":445},[380,1519,1520],{"class":416},"flat ",[380,1522,439],{"class":412},[380,1524,1525],{"class":416}," report.reset_index()\n",[380,1527,1528,1531,1533],{"class":382,"line":486},[380,1529,1530],{"class":416},"flat.columns.name ",[380,1532,439],{"class":412},[380,1534,1535],{"class":463}," None\n",[380,1537,1538,1541,1544,1546,1548,1550,1552],{"class":382,"line":536},[380,1539,1540],{"class":416},"flat.to_excel(",[380,1542,1543],{"class":390},"\"pivot_subtotals.xlsx\"",[380,1545,497],{"class":416},[380,1547,646],{"class":645},[380,1549,439],{"class":412},[380,1551,651],{"class":463},[380,1553,654],{"class":416},[380,1555,1556],{"class":382,"line":582},[380,1557,430],{"emptyLinePlaceholder":429},[380,1559,1560,1563,1565,1568,1570],{"class":382,"line":628},[380,1561,1562],{"class":416},"wb ",[380,1564,439],{"class":412},[380,1566,1567],{"class":416}," load_workbook(",[380,1569,1543],{"class":390},[380,1571,654],{"class":416},[380,1573,1574,1577,1579],{"class":382,"line":634},[380,1575,1576],{"class":416},"ws ",[380,1578,439],{"class":412},[380,1580,1581],{"class":416}," wb.active\n",[380,1583,1584,1587,1589,1592,1595,1597,1599],{"class":382,"line":657},[380,1585,1586],{"class":416},"bold ",[380,1588,439],{"class":412},[380,1590,1591],{"class":416}," Font(",[380,1593,1594],{"class":645},"bold",[380,1596,439],{"class":412},[380,1598,745],{"class":463},[380,1600,654],{"class":416},[380,1602,1603,1606,1608,1611,1614,1616,1619,1621,1624],{"class":382,"line":1041},[380,1604,1605],{"class":416},"shade ",[380,1607,439],{"class":412},[380,1609,1610],{"class":416}," PatternFill(",[380,1612,1613],{"class":390},"\"solid\"",[380,1615,497],{"class":416},[380,1617,1618],{"class":645},"fgColor",[380,1620,439],{"class":412},[380,1622,1623],{"class":390},"\"E7EBEF\"",[380,1625,654],{"class":416},[380,1627,1628,1631,1634,1636,1639,1642,1644,1646],{"class":382,"line":1052},[380,1629,1630],{"class":412},"for",[380,1632,1633],{"class":416}," row ",[380,1635,1061],{"class":412},[380,1637,1638],{"class":416}," ws.iter_rows(",[380,1640,1641],{"class":645},"min_row",[380,1643,439],{"class":412},[380,1645,144],{"class":463},[380,1647,829],{"class":416},[380,1649,1650,1653,1656,1658,1661,1664,1667,1670,1672,1674,1676,1678,1681],{"class":382,"line":1072},[380,1651,1652],{"class":412},"    if",[380,1654,1655],{"class":416}," row[",[380,1657,111],{"class":463},[380,1659,1660],{"class":416},"].value ",[380,1662,1663],{"class":412},"==",[380,1665,1666],{"class":390}," \"Subtotal\"",[380,1668,1669],{"class":412}," or",[380,1671,1655],{"class":416},[380,1673,47],{"class":463},[380,1675,1660],{"class":416},[380,1677,1663],{"class":412},[380,1679,1680],{"class":390}," \"Total\"",[380,1682,1683],{"class":416},":\n",[380,1685,1686,1689,1692,1694],{"class":382,"line":1084},[380,1687,1688],{"class":412},"        for",[380,1690,1691],{"class":416}," cell ",[380,1693,1061],{"class":412},[380,1695,1696],{"class":416}," row:\n",[380,1698,1699,1702,1704],{"class":382,"line":1099},[380,1700,1701],{"class":416},"            cell.font ",[380,1703,439],{"class":412},[380,1705,1706],{"class":416}," bold\n",[380,1708,1709,1712,1714],{"class":382,"line":1104},[380,1710,1711],{"class":416},"            cell.fill ",[380,1713,439],{"class":412},[380,1715,1716],{"class":416}," shade\n",[380,1718,1719,1722,1724],{"class":382,"line":1131},[380,1720,1721],{"class":416},"wb.save(",[380,1723,1543],{"class":390},[380,1725,654],{"class":416},[380,1727,1729,1731,1734,1736,1739],{"class":382,"line":1728},18,[380,1730,762],{"class":463},[380,1732,1733],{"class":416},"(flat.values.tolist()[",[380,1735,144],{"class":463},[380,1737,1738],{"class":416},"])     ",[380,1740,1741],{"class":1388},"# ['North', 'Subtotal', 200, 250, 450]\n",[10,1743,1744,1745,1749],{},"Repeating the region on every row keeps the sheet filterable. If readers prefer the grouped look with the region shown once, blank the repeats just before writing, as described in ",[19,1746,1748],{"href":1747},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fforward-fill-blank-cells-in-grouped-excel-reports\u002F","Forward-Fill Blank Cells in Grouped Excel Reports with pandas",".",[366,1751,1753],{"id":1752},"step-5-or-write-subtotals-as-live-formulas","Step 5 — Or write subtotals as live formulas",[10,1755,1756,1757,1760,1761,1764],{},"When people will adjust the city figures in Excel, static subtotals go stale. Write the detail values and let Excel compute the rest with ",[14,1758,1759],{},"SUBTOTAL(9, …)",", which sums a range while ignoring any other ",[14,1762,1763],{},"SUBTOTAL"," results inside it:",[371,1766,1768],{"className":403,"code":1767,"language":405,"meta":376,"style":376},"from openpyxl import Workbook\nfrom openpyxl.utils import get_column_letter\n\ndetail = sales.pivot_table(index=[\"Region\", \"City\"], columns=\"Quarter\", values=\"Revenue\", aggfunc=\"sum\")\nquarters = list(detail.columns)\nwb = Workbook()\nws = wb.active\nws.append([\"Region\", \"City\", *quarters])\nfirst_data_row = 2\nfor region, block in detail.groupby(level=\"Region\", sort=False):\n    start = ws.max_row + 1\n    for (_, city), values in block.iterrows():\n        ws.append([region, city, *values.tolist()])\n    end = ws.max_row\n    ws.append([region, \"Subtotal\", *[f\"=SUBTOTAL(9,{get_column_letter(3 + i)}{start}:{get_column_letter(3 + i)}{end})\"\n                                     for i in range(len(quarters))]])\nlast = ws.max_row\nws.append([\"Total\", \"\", *[f\"=SUBTOTAL(9,{get_column_letter(3 + i)}{first_data_row}:{get_column_letter(3 + i)}{last})\"\n                          for i in range(len(quarters))]])\nwb.save(\"pivot_formulas.xlsx\")\nprint(ws[\"C4\"].value, ws[\"C8\"].value)     # =SUBTOTAL(9,C2:C3) =SUBTOTAL(9,C2:C7)\n",[14,1769,1770,1781,1793,1797,1844,1857,1866,1874,1892,1902,1931,1946,1958,1968,1978,2043,2065,2074,2132,2150,2160],{"__ignoreMap":376},[380,1771,1772,1774,1776,1778],{"class":382,"line":383},[380,1773,1491],{"class":412},[380,1775,1494],{"class":416},[380,1777,413],{"class":412},[380,1779,1780],{"class":416}," Workbook\n",[380,1782,1783,1785,1788,1790],{"class":382,"line":426},[380,1784,1491],{"class":412},[380,1786,1787],{"class":416}," openpyxl.utils ",[380,1789,413],{"class":412},[380,1791,1792],{"class":416}," get_column_letter\n",[380,1794,1795],{"class":382,"line":433},[380,1796,430],{"emptyLinePlaceholder":429},[380,1798,1799,1802,1804,1806,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842],{"class":382,"line":445},[380,1800,1801],{"class":416},"detail ",[380,1803,439],{"class":412},[380,1805,687],{"class":416},[380,1807,646],{"class":645},[380,1809,439],{"class":412},[380,1811,694],{"class":416},[380,1813,697],{"class":390},[380,1815,497],{"class":416},[380,1817,702],{"class":390},[380,1819,705],{"class":416},[380,1821,708],{"class":645},[380,1823,439],{"class":412},[380,1825,713],{"class":390},[380,1827,497],{"class":416},[380,1829,718],{"class":645},[380,1831,439],{"class":412},[380,1833,723],{"class":390},[380,1835,497],{"class":416},[380,1837,1357],{"class":645},[380,1839,439],{"class":412},[380,1841,735],{"class":390},[380,1843,654],{"class":416},[380,1845,1846,1849,1851,1854],{"class":382,"line":486},[380,1847,1848],{"class":416},"quarters ",[380,1850,439],{"class":412},[380,1852,1853],{"class":463}," list",[380,1855,1856],{"class":416},"(detail.columns)\n",[380,1858,1859,1861,1863],{"class":382,"line":536},[380,1860,1562],{"class":416},[380,1862,439],{"class":412},[380,1864,1865],{"class":416}," Workbook()\n",[380,1867,1868,1870,1872],{"class":382,"line":582},[380,1869,1576],{"class":416},[380,1871,439],{"class":412},[380,1873,1581],{"class":416},[380,1875,1876,1879,1881,1883,1885,1887,1889],{"class":382,"line":628},[380,1877,1878],{"class":416},"ws.append([",[380,1880,697],{"class":390},[380,1882,497],{"class":416},[380,1884,702],{"class":390},[380,1886,497],{"class":416},[380,1888,460],{"class":412},[380,1890,1891],{"class":416},"quarters])\n",[380,1893,1894,1897,1899],{"class":382,"line":634},[380,1895,1896],{"class":416},"first_data_row ",[380,1898,439],{"class":412},[380,1900,1901],{"class":463}," 2\n",[380,1903,1904,1906,1909,1911,1914,1916,1918,1920,1922,1925,1927,1929],{"class":382,"line":657},[380,1905,1630],{"class":412},[380,1907,1908],{"class":416}," region, block ",[380,1910,1061],{"class":412},[380,1912,1913],{"class":416}," detail.groupby(",[380,1915,914],{"class":645},[380,1917,439],{"class":412},[380,1919,697],{"class":390},[380,1921,497],{"class":416},[380,1923,1924],{"class":645},"sort",[380,1926,439],{"class":412},[380,1928,651],{"class":463},[380,1930,829],{"class":416},[380,1932,1933,1936,1938,1941,1943],{"class":382,"line":1041},[380,1934,1935],{"class":416},"    start ",[380,1937,439],{"class":412},[380,1939,1940],{"class":416}," ws.max_row ",[380,1942,1093],{"class":412},[380,1944,1945],{"class":463}," 1\n",[380,1947,1948,1950,1953,1955],{"class":382,"line":1052},[380,1949,1055],{"class":412},[380,1951,1952],{"class":416}," (_, city), values ",[380,1954,1061],{"class":412},[380,1956,1957],{"class":416}," block.iterrows():\n",[380,1959,1960,1963,1965],{"class":382,"line":1072},[380,1961,1962],{"class":416},"        ws.append([region, city, ",[380,1964,460],{"class":412},[380,1966,1967],{"class":416},"values.tolist()])\n",[380,1969,1970,1973,1975],{"class":382,"line":1084},[380,1971,1972],{"class":416},"    end ",[380,1974,439],{"class":412},[380,1976,1977],{"class":416}," ws.max_row\n",[380,1979,1980,1983,1985,1987,1989,1991,1994,1997,2001,2004,2006,2008,2011,2014,2017,2020,2023,2025,2027,2029,2031,2033,2035,2038,2040],{"class":382,"line":1099},[380,1981,1982],{"class":416},"    ws.append([region, ",[380,1984,819],{"class":390},[380,1986,497],{"class":416},[380,1988,460],{"class":412},[380,1990,694],{"class":416},[380,1992,1993],{"class":412},"f",[380,1995,1996],{"class":390},"\"=SUBTOTAL(9,",[380,1998,2000],{"class":1999},"sSjpA","{",[380,2002,2003],{"class":416},"get_column_letter(",[380,2005,177],{"class":463},[380,2007,467],{"class":412},[380,2009,2010],{"class":416}," i)",[380,2012,2013],{"class":1999},"}{",[380,2015,2016],{"class":416},"start",[380,2018,2019],{"class":1999},"}",[380,2021,2022],{"class":390},":",[380,2024,2000],{"class":1999},[380,2026,2003],{"class":416},[380,2028,177],{"class":463},[380,2030,467],{"class":412},[380,2032,2010],{"class":416},[380,2034,2013],{"class":1999},[380,2036,2037],{"class":416},"end",[380,2039,2019],{"class":1999},[380,2041,2042],{"class":390},")\"\n",[380,2044,2045,2048,2051,2053,2056,2059,2062],{"class":382,"line":1104},[380,2046,2047],{"class":412},"                                     for",[380,2049,2050],{"class":416}," i ",[380,2052,1061],{"class":412},[380,2054,2055],{"class":463}," range",[380,2057,2058],{"class":416},"(",[380,2060,2061],{"class":463},"len",[380,2063,2064],{"class":416},"(quarters))]])\n",[380,2066,2067,2070,2072],{"class":382,"line":1131},[380,2068,2069],{"class":416},"last ",[380,2071,439],{"class":412},[380,2073,1977],{"class":416},[380,2075,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102,2104,2106,2109,2111,2113,2115,2117,2119,2121,2123,2125,2128,2130],{"class":382,"line":1728},[380,2077,1878],{"class":416},[380,2079,755],{"class":390},[380,2081,497],{"class":416},[380,2083,1029],{"class":390},[380,2085,497],{"class":416},[380,2087,460],{"class":412},[380,2089,694],{"class":416},[380,2091,1993],{"class":412},[380,2093,1996],{"class":390},[380,2095,2000],{"class":1999},[380,2097,2003],{"class":416},[380,2099,177],{"class":463},[380,2101,467],{"class":412},[380,2103,2010],{"class":416},[380,2105,2013],{"class":1999},[380,2107,2108],{"class":416},"first_data_row",[380,2110,2019],{"class":1999},[380,2112,2022],{"class":390},[380,2114,2000],{"class":1999},[380,2116,2003],{"class":416},[380,2118,177],{"class":463},[380,2120,467],{"class":412},[380,2122,2010],{"class":416},[380,2124,2013],{"class":1999},[380,2126,2127],{"class":416},"last",[380,2129,2019],{"class":1999},[380,2131,2042],{"class":390},[380,2133,2135,2138,2140,2142,2144,2146,2148],{"class":382,"line":2134},19,[380,2136,2137],{"class":412},"                          for",[380,2139,2050],{"class":416},[380,2141,1061],{"class":412},[380,2143,2055],{"class":463},[380,2145,2058],{"class":416},[380,2147,2061],{"class":463},[380,2149,2064],{"class":416},[380,2151,2153,2155,2158],{"class":382,"line":2152},20,[380,2154,1721],{"class":416},[380,2156,2157],{"class":390},"\"pivot_formulas.xlsx\"",[380,2159,654],{"class":416},[380,2161,2163,2165,2168,2171,2174,2177,2180],{"class":382,"line":2162},21,[380,2164,762],{"class":463},[380,2166,2167],{"class":416},"(ws[",[380,2169,2170],{"class":390},"\"C4\"",[380,2172,2173],{"class":416},"].value, ws[",[380,2175,2176],{"class":390},"\"C8\"",[380,2178,2179],{"class":416},"].value)     ",[380,2181,2182],{"class":1388},"# =SUBTOTAL(9,C2:C3) =SUBTOTAL(9,C2:C7)\n",[10,2184,2185,2186,2188,2189,1749],{},"The grand-total formula spans every row, subtotals included, and still returns 490 for Q1 — recalculating the file in LibreOffice confirms 200 and 290 for the Q1 subtotals and 490 for the total. ",[14,2187,1763],{}," skips the nested subtotal cells, so nothing is counted twice. Use function number 1 for averages, 2 for counts and 4 for maximums, but remember Step 3: an average of a range of detail cells is an average of cities, not of orders. Formulas are best for sums. Calculating them outside Excel to verify the file is covered in ",[19,2190,2192],{"href":2191},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frecalculate-excel-formulas-without-excel-in-python\u002F","Recalculate Excel Formulas Without Excel in Python",[25,2194,34,2199,34,2202,34,2205,34,2208,34,2212,34,2218,34,2221,34,2225,34,2229,34,2232,34,2235,34,2243,34,2248,34,2252,34,2256,34,2259,34,2261,34,2264,34,2268,34,2271,34,2274,34,2276,34,2279,34,2281,34,2284,34,2288,34,2291,34,2294,34,2296],{"viewBox":2195,"role":28,"ariaLabelledBy":2196,"xmlns":32,"style":1156},"4 0 752 238",[2197,2198],"st-choose-t","st-choose-d",[36,2200,2201],{"id":2197},"Static values or SUBTOTAL formulas",[40,2203,2204],{"id":2198},"Use static subtotal values computed in pandas when the measure is an average, median or distinct count, or when the report is final. Use SUBTOTAL formulas when the measure is a sum and readers will edit the detail figures in Excel.",[44,2206],{"x":208,"y":47,"width":1165,"height":2207,"fill":50},"238",[44,2209],{"x":2210,"y":54,"width":2211,"height":1273,"rx":1172,"fill":337,"stroke":338,"style":1173},"191.0","378.0",[1175,2213,2215],{"transform":2214},"translate(368.0,29.0) scale(1.000)",[1179,2216],{"d":2217,"fill":1182,"stroke":1183,"style":1184},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[65,2219,2220],{"x":361,"y":1273,"style":1189},"Will readers edit the numbers, and is it a sum?",[382,2222],{"x1":361,"y1":2223,"x2":361,"y2":2224,"stroke":338,"style":1173},"91.5","111.5",[382,2226],{"x1":2227,"y1":2224,"x2":2228,"y2":2224,"stroke":338,"style":1173},"134.7","625.3",[382,2230],{"x1":2227,"y1":2224,"x2":2227,"y2":2231,"stroke":338,"style":1173},"157.5",[1202,2233],{"points":2234,"fill":1205},"134.7,161.5 129.7,152.5 139.7,152.5",[44,2236],{"x":2237,"y":2238,"width":2239,"height":2240,"rx":2241,"fill":50,"stroke":58,"style":2242},"93.1","121.5","83.2","21.0","10","stroke-width:1.2px",[65,2244,2247],{"x":2227,"y":2245,"style":2246},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","final report",[44,2249],{"x":1169,"y":2250,"width":2251,"height":110,"rx":1172,"fill":337,"stroke":338,"style":1173},"163.5","229.3",[65,2253,2255],{"x":2227,"y":2254,"style":1189},"189.0","static values",[65,2257,1473],{"x":2227,"y":2258,"style":1194},"208.5",[382,2260],{"x1":361,"y1":2224,"x2":361,"y2":2231,"stroke":338,"style":1173},[1202,2262],{"points":2263,"fill":1205},"380.0,161.5 375.0,152.5 385.0,152.5",[44,2265],{"x":2266,"y":2238,"width":2267,"height":2240,"rx":2241,"fill":50,"stroke":58,"style":2242},"303.7","152.6",[65,2269,2270],{"x":361,"y":2245,"style":2246},"mean, median, distinct",[44,2272],{"x":2273,"y":2250,"width":2251,"height":110,"rx":1172,"fill":1261,"stroke":1262,"style":1173},"265.3",[65,2275,2255],{"x":361,"y":2254,"style":1274},[65,2277,2278],{"x":361,"y":2258,"style":1194},"from raw rows",[382,2280],{"x1":2228,"y1":2224,"x2":2228,"y2":2231,"stroke":338,"style":1173},[1202,2282],{"points":2283,"fill":1205},"625.3,161.5 620.3,152.5 630.3,152.5",[44,2285],{"x":2286,"y":2238,"width":2287,"height":2240,"rx":2241,"fill":50,"stroke":58,"style":2242},"575.1","100.5",[65,2289,2290],{"x":2228,"y":2245,"style":2246},"editable sums",[44,2292],{"x":2293,"y":2250,"width":2251,"height":110,"rx":1172,"fill":211,"stroke":212,"style":1173},"510.7",[65,2295,1759],{"x":2228,"y":2254,"style":1219},[65,2297,2298],{"x":2228,"y":2258,"style":1194},"stays live",[366,2300,2302],{"id":2301},"step-6-check-that-the-pieces-add-up","Step 6 — Check that the pieces add up",[10,2304,2305],{},"A report with subtotals makes three claims that must agree: the cities add up to each subtotal, the subtotals add up to the grand total, and the grand total matches the source data. For sums, test all three before the file goes out:",[371,2307,2309],{"className":403,"code":2308,"language":405,"meta":376,"style":376},"is_sub = report.index.get_level_values(\"City\") == \"Subtotal\"\nis_total = report.index.get_level_values(\"Region\") == \"Total\"\ncities = report[~is_sub & ~is_total]\nsubs = report[is_sub]\n\nby_region = cities.groupby(level=\"Region\", sort=False).sum()\nassert (by_region.values == subs.values).all(), \"cities do not add up to subtotals\"\nassert (subs.sum().values == report[is_total].values).all(), \"subtotals do not add up to the total\"\nassert report.loc[is_total, \"Total\"].item() == sales[\"Revenue\"].sum(), \"total differs from source\"\nprint(\"checks passed:\", report.loc[is_total, \"Total\"].item())      # checks passed: 1050\n",[14,2310,2311,2331,2349,2373,2383,2387,2414,2430,2445,2470],{"__ignoreMap":376},[380,2312,2313,2316,2318,2321,2323,2326,2328],{"class":382,"line":383},[380,2314,2315],{"class":416},"is_sub ",[380,2317,439],{"class":412},[380,2319,2320],{"class":416}," report.index.get_level_values(",[380,2322,702],{"class":390},[380,2324,2325],{"class":416},") ",[380,2327,1663],{"class":412},[380,2329,2330],{"class":390}," \"Subtotal\"\n",[380,2332,2333,2336,2338,2340,2342,2344,2346],{"class":382,"line":426},[380,2334,2335],{"class":416},"is_total ",[380,2337,439],{"class":412},[380,2339,2320],{"class":416},[380,2341,697],{"class":390},[380,2343,2325],{"class":416},[380,2345,1663],{"class":412},[380,2347,2348],{"class":390}," \"Total\"\n",[380,2350,2351,2354,2356,2359,2362,2364,2367,2370],{"class":382,"line":433},[380,2352,2353],{"class":416},"cities ",[380,2355,439],{"class":412},[380,2357,2358],{"class":416}," report[",[380,2360,2361],{"class":412},"~",[380,2363,2315],{"class":416},[380,2365,2366],{"class":412},"&",[380,2368,2369],{"class":412}," ~",[380,2371,2372],{"class":416},"is_total]\n",[380,2374,2375,2378,2380],{"class":382,"line":445},[380,2376,2377],{"class":416},"subs ",[380,2379,439],{"class":412},[380,2381,2382],{"class":416}," report[is_sub]\n",[380,2384,2385],{"class":382,"line":486},[380,2386,430],{"emptyLinePlaceholder":429},[380,2388,2389,2392,2394,2397,2399,2401,2403,2405,2407,2409,2411],{"class":382,"line":536},[380,2390,2391],{"class":416},"by_region ",[380,2393,439],{"class":412},[380,2395,2396],{"class":416}," cities.groupby(",[380,2398,914],{"class":645},[380,2400,439],{"class":412},[380,2402,697],{"class":390},[380,2404,497],{"class":416},[380,2406,1924],{"class":645},[380,2408,439],{"class":412},[380,2410,651],{"class":463},[380,2412,2413],{"class":416},").sum()\n",[380,2415,2416,2419,2422,2424,2427],{"class":382,"line":582},[380,2417,2418],{"class":412},"assert",[380,2420,2421],{"class":416}," (by_region.values ",[380,2423,1663],{"class":412},[380,2425,2426],{"class":416}," subs.values).all(), ",[380,2428,2429],{"class":390},"\"cities do not add up to subtotals\"\n",[380,2431,2432,2434,2437,2439,2442],{"class":382,"line":628},[380,2433,2418],{"class":412},[380,2435,2436],{"class":416}," (subs.sum().values ",[380,2438,1663],{"class":412},[380,2440,2441],{"class":416}," report[is_total].values).all(), ",[380,2443,2444],{"class":390},"\"subtotals do not add up to the total\"\n",[380,2446,2447,2449,2452,2454,2457,2459,2462,2464,2467],{"class":382,"line":634},[380,2448,2418],{"class":412},[380,2450,2451],{"class":416}," report.loc[is_total, ",[380,2453,755],{"class":390},[380,2455,2456],{"class":416},"].item() ",[380,2458,1663],{"class":412},[380,2460,2461],{"class":416}," sales[",[380,2463,723],{"class":390},[380,2465,2466],{"class":416},"].sum(), ",[380,2468,2469],{"class":390},"\"total differs from source\"\n",[380,2471,2472,2474,2476,2479,2482,2484,2487],{"class":382,"line":657},[380,2473,762],{"class":463},[380,2475,2058],{"class":416},[380,2477,2478],{"class":390},"\"checks passed:\"",[380,2480,2481],{"class":416},", report.loc[is_total, ",[380,2483,755],{"class":390},[380,2485,2486],{"class":416},"].item())      ",[380,2488,2489],{"class":1388},"# checks passed: 1050\n",[10,2491,2492],{},"The boolean masks built from the index levels are also how you separate detail, subtotal and total rows for any further processing — charting only the cities, for example, or exporting the subtotals as a summary sheet. Skip the first two checks for averages and other non-additive measures, where the parts are not supposed to add up; the third still applies with the matching aggregation of the source.",[366,2494,2496],{"id":2495},"common-pitfalls","Common pitfalls",[2498,2499,2500,2512,2518,2531,2537],"ul",{},[2501,2502,2503,2507,2508,2511],"li",{},[2504,2505,2506],"strong",{},"Summing the subtotal rows again."," Once subtotals are in the frame, ",[14,2509,2510],{},"report.sum()"," counts every value twice. Filter them out, or compute totals before adding them.",[2501,2513,2514,2517],{},[2504,2515,2516],{},"Sorting after adding subtotals."," An alphabetical sort moves \"Subtotal\" between cities. Sort before, or sort by region and a subtotal flag.",[2501,2519,2520,2523,2524,2527,2528,2530],{},[2504,2521,2522],{},"Using SUM in formula totals."," ",[14,2525,2526],{},"=SUM(C2:C7)"," over a range containing subtotals double-counts; use ",[14,2529,1759],{}," or reference only the subtotal cells.",[2501,2532,2533,2536],{},[2504,2534,2535],{},"Empty groups after filtering."," If a filter removes every row for one city, it disappears from the detail pivot but the region subtotal still covers the remaining cities correctly, because both are built from the same filtered rows. Filter the raw data, never the finished report.",[2501,2538,2539,2542],{},[2504,2540,2541],{},"Mismatched labels."," A subtotal labelled \"Subtotal\" in one report and \"Total\" in another breaks every downstream filter. Pick names once.",[366,2544,2546],{"id":2545},"conclusion","Conclusion",[10,2548,2549,2551,2552,2554],{},[14,2550,1142],{}," provides the grand total; subtotals need a second pivot on the outer level, relabelled and interleaved with the detail rows. Aggregating each subtotal from the raw data keeps averages and counts correct. Write the result with styled subtotal rows for a finished report, or with ",[14,2553,1759],{}," formulas when readers will change the figures in Excel.",[366,2556,2558],{"id":2557},"frequently-asked-questions","Frequently asked questions",[10,2560,2561,2564],{},[2504,2562,2563],{},"Does pandas pivot_table support subtotals?","\nNot directly. margins=True adds one grand-total row and column, but no totals for each outer group of a multi-level index. Build subtotal rows with a second pivot on the outer level and combine them with the detail rows.",[10,2566,2567,2570],{},[2504,2568,2569],{},"Why are my subtotal averages wrong?","\nAveraging the group averages weights every city equally, however many rows it has. Compute each subtotal from the raw rows of that group — a second pivot_table on the outer level does exactly that.",[10,2572,2573,2576],{},[2504,2574,2575],{},"How do I keep subtotals in the right place when sorting?","\nSort by the outer level first and then by a flag that is true for subtotal rows, so each subtotal follows its group. Or build the table group by group in the order you want.",[10,2578,2579,2582],{},[2504,2580,2581],{},"Can the subtotals update when someone edits the numbers in Excel?","\nYes, if you write them as formulas. Use SUBTOTAL(9, range) for each group and for the grand total; SUBTOTAL ignores other SUBTOTAL results inside its range, so the grand total is not double counted.",[366,2584,2586],{"id":2585},"related","Related",[2498,2588,2589,2595,2602,2609],{},[2501,2590,2591,2592,2594],{},"Up one level: ",[19,2593,22],{"href":21}," — pivots from Excel sheets.",[2501,2596,2597,2601],{},[19,2598,2600],{"href":2599},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a Pivot Table from Excel with pandas"," — the basics of pivot_table.",[2501,2603,2604,2608],{},[19,2605,2607],{"href":2606},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python\u002F","Add a Native Excel Pivot Table with Python"," — when readers need Excel's own pivot tools.",[2501,2610,2611,2615],{},[19,2612,2614],{"href":2613},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002F","SUMIF and SUMIFS Equivalent in pandas"," — conditional totals without a pivot.",[2617,2618,2619],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":376,"searchDepth":426,"depth":426,"links":2621},[2622,2623,2624,2625,2626,2627,2628,2629,2630,2631,2632],{"id":368,"depth":426,"text":369},{"id":671,"depth":426,"text":672},{"id":782,"depth":426,"text":783},{"id":1314,"depth":426,"text":1315},{"id":1477,"depth":426,"text":1478},{"id":1752,"depth":426,"text":1753},{"id":2301,"depth":426,"text":2302},{"id":2495,"depth":426,"text":2496},{"id":2545,"depth":426,"text":2546},{"id":2557,"depth":426,"text":2558},{"id":2585,"depth":426,"text":2586},"2026-09-23","pandas' margins=True adds only a grand total. Add Excel-style subtotal rows for each outer group of a multi-level pivot, compute them from the raw data so averages stay correct, and write them to Excel as bold rows or live SUBTOTAL formulas.","md",[2637,2639,2641,2643],{"q":2563,"a":2638},"Not directly. margins=True adds one grand-total row and column, but no totals for each outer group of a multi-level index. Build subtotal rows with a second pivot on the outer level and combine them with the detail rows.",{"q":2569,"a":2640},"Averaging the group averages weights every city equally, however many rows it has. Compute each subtotal from the raw rows of that group — a second pivot_table on the outer level does exactly that.",{"q":2575,"a":2642},"Sort by the outer level first and then by a flag that is true for subtotal rows, so each subtotal follows its group. Or build the table group by group in the order you want.",{"q":2581,"a":2644},"Yes, if you write them as formulas. Use SUBTOTAL(9, range) for each group and for the grand total; SUBTOTAL ignores other SUBTOTAL results inside its range, so the grand total is not double counted.","add subtotals and grand totals to a pandas pivot table",null,{"breadcrumb":2648},[2649,2652,2655],{"name":2650,"item":2651},"Home","\u002F",{"name":2653,"item":2654},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2656,"item":21},"Creating Pivot Tables from Excel Data with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table",{"title":2659,"description":2660},"Add Subtotals to a pandas Pivot Table (Excel-style group totals)","Add subtotal rows per group to a multi-level pandas pivot_table, keep averages and counts correct by aggregating raw data, order subtotals after each group, and export bold subtotal rows or SUBTOTAL formulas to Excel.","add-subtotals-and-grand-totals-to-a-pandas-pivot-table","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table\u002Findex",[2664,2665],"pivots-and-summaries","pandas","how-to","syTSzw3Q-4TtjXdt7wGErrHd8fX9rkjk5w20u94Cq10",[2669,2672],{"title":2607,"path":2670,"stem":2671,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python\u002Findex",{"title":2673,"path":2674,"stem":2675,"children":-1},"Bin Values into Ranges Like Excel FREQUENCY with pd.cut","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut\u002Findex",[2677,2679],{"path":2678,"title":2656},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data",{"path":2680,"title":2653},"\u002Fadvanced-data-transformation-and-cleaning",[],[2683,2686,2689,2690,2691,2694],{"path":2684,"title":2685,"kind":2666},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-churn-report-from-subscription-data","Build a Customer Churn Report from Subscription Data",{"path":2687,"title":2688,"kind":2666},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Frefresh-pivot-tables-and-queries-in-excel-with-python","Refresh Excel Pivot Tables and Queries with Python",{"path":2670,"title":2607,"kind":2666},{"path":2674,"title":2673,"kind":2666},{"path":2692,"title":2693,"kind":2666},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-cohort-retention-table-from-excel-data","Build a Cohort Retention Table from Excel Data",{"path":2695,"title":2696,"kind":2666},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbuild-a-crosstab-from-excel-data-with-pandas","Build a Crosstab from Excel Data with pandas",1790972086134]