[{"data":1,"prerenderedAt":1756},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas":1722,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas":1731,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas":1736,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas":1737},{"id":4,"title":5,"body":6,"dateModified":1689,"datePublished":1689,"description":1690,"extension":1691,"faq":1692,"keyword":1701,"kind":1702,"meta":1703,"navigation":485,"path":1712,"seo":1713,"slug":1715,"stem":1716,"topics":1717,"type":1720,"__hash__":1721},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas\u002Findex.md","SUBTOTAL and AGGREGATE Equivalents in pandas",{"type":7,"value":8,"toc":1677},"minimark",[9,39,44,72,90,94,202,235,442,446,738,769,773,1031,1072,1171,1175,1384,1444,1451,1455,1471,1534,1538,1582,1586,1601,1605,1615,1621,1630,1640,1644,1673],[10,11,12,13,17,18,20,21,24,25,28,29,32,33,38],"p",{},"Two Excel functions exist mainly to cope with messy sheets. ",[14,15,16],"code",{},"SUBTOTAL"," sums, averages or counts a range while ignoring other ",[14,19,16],{}," results inside it — so a grand total over a column that already contains regional subtotals does not count everything twice — and, with function numbers from 101 upwards, it also ignores rows someone has hidden. ",[14,22,23],{},"AGGREGATE"," goes further and can ignore error values such as ",[14,26,27],{},"#DIV\u002F0!"," and ",[14,30,31],{},"#N\u002FA",". When a workbook like that is read into pandas, those rules disappear: the subtotal rows become ordinary rows, the hidden rows are back, and errors arrive as text. This page rebuilds each behaviour in pandas, using openpyxl to find out which rows were subtotals and which were hidden, and compares every number with LibreOffice's calculation of the original formulas. It belongs to ",[34,35,37],"a",{"href":36},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[40,41,43],"h2",{"id":42},"prerequisites","Prerequisites",[45,46,51],"pre",{"className":47,"code":48,"language":49,"meta":50,"style":50},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,52,53],{"__ignoreMap":50},[54,55,58,62,66,69],"span",{"class":56,"line":57},"line",1,[54,59,61],{"class":60},"sMTad","pip",[54,63,65],{"class":64},"srMev"," install",[54,67,68],{"class":64}," pandas",[54,70,71],{"class":64}," openpyxl\n",[10,73,74,75,79,80,82,83,86,87,89],{},"The workbook has two sheets. ",[76,77,78],"em",{},"Sales"," lists reps by region with a ",[14,81,16],{}," row after each region and a grand total, and the row for Ben is hidden. ",[76,84,85],{},"Meters"," has five readings, two of which are errors — a division by zero and an ",[14,88,31],{},". Next to each table are the Excel formulas being reproduced. LibreOffice recalculates them for comparison.",[40,91,93],{"id":92},"step-1-what-excel-calculates","Step 1 — What Excel calculates",[45,95,97],{"className":47,"code":96,"language":49,"meta":50,"style":50},"soffice --headless --convert-to 'csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,true,false,false,-1' sales_subtotals.xlsx > \u002Fdev\u002Fnull 2>&1\ncut -d, -f5- sales_subtotals-Sales.csv | head -6\ncut -d, -f4- sales_subtotals-Meters.csv | head -5\nsoffice --headless --convert-to xlsx --outdir calculated sales_subtotals.xlsx > \u002Fdev\u002Fnull 2>&1   # a copy with results stored\n",[14,98,99,127,151,171],{"__ignoreMap":50},[54,100,101,104,108,111,114,117,121,124],{"class":56,"line":57},[54,102,103],{"class":60},"soffice",[54,105,107],{"class":106},"sP0c6"," --headless",[54,109,110],{"class":106}," --convert-to",[54,112,113],{"class":64}," 'csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,true,false,false,-1'",[54,115,116],{"class":64}," sales_subtotals.xlsx",[54,118,120],{"class":119},"s-kum"," >",[54,122,123],{"class":64}," \u002Fdev\u002Fnull",[54,125,126],{"class":119}," 2>&1\n",[54,128,130,133,136,139,142,145,148],{"class":56,"line":129},2,[54,131,132],{"class":60},"cut",[54,134,135],{"class":106}," -d,",[54,137,138],{"class":106}," -f5-",[54,140,141],{"class":64}," sales_subtotals-Sales.csv",[54,143,144],{"class":119}," |",[54,146,147],{"class":60}," head",[54,149,150],{"class":106}," -6\n",[54,152,154,156,158,161,164,166,168],{"class":56,"line":153},3,[54,155,132],{"class":60},[54,157,135],{"class":106},[54,159,160],{"class":106}," -f4-",[54,162,163],{"class":64}," sales_subtotals-Meters.csv",[54,165,144],{"class":119},[54,167,147],{"class":60},[54,169,170],{"class":106}," -5\n",[54,172,174,176,178,180,183,186,189,191,193,195,198],{"class":56,"line":173},4,[54,175,103],{"class":60},[54,177,107],{"class":106},[54,179,110],{"class":106},[54,181,182],{"class":64}," xlsx",[54,184,185],{"class":106}," --outdir",[54,187,188],{"class":64}," calculated",[54,190,116],{"class":64},[54,192,120],{"class":119},[54,194,123],{"class":64},[54,196,197],{"class":119}," 2>&1",[54,199,201],{"class":200},"s-wDw","   # a copy with results stored\n",[10,203,204,205,207,208,211,212,215,216,218,219,222,223,225,226,228,229,231,232,234],{},"These are the targets. On ",[76,206,78],{},", a plain ",[14,209,210],{},"SUM"," over C2:C8 gives 7,980 — every rep plus both regional subtotals, so everything counted twice. ",[14,213,214],{},"SUBTOTAL(9, …)"," gives 3,990, because it skips the cells that are themselves ",[14,217,16],{}," results. ",[14,220,221],{},"SUBTOTAL(109, …)"," gives 3,190: it also skips Ben's hidden row. The averages follow the same pattern — 798 over five reps, 797.5 over the four visible ones — and the maximum is 1,200. On ",[76,224,85],{},", ",[14,227,210],{}," returns ",[14,230,31],{}," because one error poisons the whole calculation, while ",[14,233,23],{}," with option 6 ignores the errors: a sum of 425, an average of 141.67, a second-largest value of 120 and a median of 120.",[236,237,245,246,245,250,245,254,245,262,245,271,245,275,245,282,245,286,245,290,245,294,245,298,245,302,245,307,245,312,245,316,245,318,245,321,245,323,245,326,245,329,245,333,245,335,245,339,245,343,245,347,245,349,245,352,245,355,245,359,245,361,245,364,245,368,245,372,245,374,245,377,245,380,245,384,245,386,245,389,245,391,245,394,245,396,245,399,245,402,245,406,245,408,245,411,245,413,245,415,245,417,245,420,245,423,245,427,245,429,245,432,245,434,245,437,245,439],"svg",{"viewBox":238,"role":239,"ariaLabelledBy":240,"xmlns":243,"style":244},"139 0 481 224","img",[241,242],"sub-targets-t","sub-targets-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:481px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[247,248,249],"title",{"id":241},"The Excel results to reproduce",[251,252,253],"desc",{"id":242},"SUM double counts the subtotals and gives 7,980. SUBTOTAL 9 skips them and gives 3,990. SUBTOTAL 109 also skips the hidden row and gives 3,190. On the meter readings, SUM returns N\u002FA because of the errors, while AGGREGATE with option 6 sums the valid readings to 425.",[255,256],"rect",{"x":257,"y":258,"width":259,"height":260,"fill":261},"139","0","481","224","#ffffff",[255,263],{"x":264,"y":265,"width":266,"height":267,"rx":258,"fill":268,"stroke":269,"style":270},"155.9","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[255,272],{"x":273,"y":265,"width":274,"height":267,"rx":258,"fill":268,"stroke":269,"style":270},"187.9","174.4",[276,277,281],"text",{"x":278,"y":279,"style":280},"275.1","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[255,283],{"x":284,"y":265,"width":285,"height":267,"rx":258,"fill":268,"stroke":269,"style":270},"362.3","62.7",[276,287,289],{"x":288,"y":279,"style":280},"393.7","B",[255,291],{"x":292,"y":265,"width":293,"height":267,"rx":258,"fill":268,"stroke":269,"style":270},"425.0","179.1",[276,295,297],{"x":296,"y":279,"style":280},"514.6","C",[255,299],{"x":264,"y":300,"width":266,"height":301,"rx":258,"fill":268,"stroke":269,"style":270},"40.0","28.0",[276,303,306],{"x":304,"y":305,"style":280},"171.9","59.0","1",[255,308],{"x":273,"y":300,"width":274,"height":301,"rx":258,"fill":309,"stroke":310,"style":311},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[276,313,315],{"x":278,"y":305,"style":314},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Formula",[255,317],{"x":284,"y":300,"width":285,"height":301,"rx":258,"fill":309,"stroke":310,"style":311},[276,319,320],{"x":288,"y":305,"style":314},"Result",[255,322],{"x":292,"y":300,"width":293,"height":301,"rx":258,"fill":309,"stroke":310,"style":311},[276,324,325],{"x":296,"y":305,"style":314},"Why",[255,327],{"x":264,"y":328,"width":266,"height":301,"rx":258,"fill":268,"stroke":269,"style":270},"68.0",[276,330,332],{"x":304,"y":331,"style":280},"87.0","2",[255,334],{"x":273,"y":328,"width":274,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,336,338],{"x":278,"y":331,"style":337},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","SUM(C2:C8)",[255,340],{"x":284,"y":328,"width":285,"height":301,"rx":258,"fill":341,"stroke":342,"style":311},"#fce9e9","var(--danger,#dc2626)",[276,344,346],{"x":288,"y":331,"style":345},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","7,980",[255,348],{"x":292,"y":328,"width":293,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,350,351],{"x":296,"y":331,"style":337},"subtotals counted twice",[255,353],{"x":264,"y":354,"width":266,"height":301,"rx":258,"fill":268,"stroke":269,"style":270},"96.0",[276,356,358],{"x":304,"y":357,"style":280},"115.0","3",[255,360],{"x":273,"y":354,"width":274,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,362,363],{"x":278,"y":357,"style":337},"SUBTOTAL(9,C2:C8)",[255,365],{"x":284,"y":354,"width":285,"height":301,"rx":258,"fill":366,"stroke":367,"style":311},"#d9f4f1","var(--teal,#0f9488)",[276,369,371],{"x":288,"y":357,"style":370},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","3,990",[255,373],{"x":292,"y":354,"width":293,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,375,376],{"x":296,"y":357,"style":337},"nested subtotals skipped",[255,378],{"x":264,"y":379,"width":266,"height":301,"rx":258,"fill":268,"stroke":269,"style":270},"124.0",[276,381,383],{"x":304,"y":382,"style":280},"143.0","4",[255,385],{"x":273,"y":379,"width":274,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,387,388],{"x":278,"y":382,"style":337},"SUBTOTAL(109,C2:C8)",[255,390],{"x":284,"y":379,"width":285,"height":301,"rx":258,"fill":366,"stroke":367,"style":311},[276,392,393],{"x":288,"y":382,"style":370},"3,190",[255,395],{"x":292,"y":379,"width":293,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,397,398],{"x":296,"y":382,"style":337},"hidden row skipped too",[255,400],{"x":264,"y":401,"width":266,"height":301,"rx":258,"fill":268,"stroke":269,"style":270},"152.0",[276,403,405],{"x":304,"y":404,"style":280},"171.0","5",[255,407],{"x":273,"y":401,"width":274,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,409,410],{"x":278,"y":404,"style":337},"SUM(B2:B6)",[255,412],{"x":284,"y":401,"width":285,"height":301,"rx":258,"fill":341,"stroke":342,"style":311},[276,414,31],{"x":288,"y":404,"style":345},[255,416],{"x":292,"y":401,"width":293,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,418,419],{"x":296,"y":404,"style":337},"errors propagate",[255,421],{"x":264,"y":422,"width":266,"height":301,"rx":258,"fill":268,"stroke":269,"style":270},"180.0",[276,424,426],{"x":304,"y":425,"style":280},"199.0","6",[255,428],{"x":273,"y":422,"width":274,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,430,431],{"x":278,"y":425,"style":337},"AGGREGATE(9,6,B2:B6)",[255,433],{"x":284,"y":422,"width":285,"height":301,"rx":258,"fill":366,"stroke":367,"style":311},[276,435,436],{"x":288,"y":425,"style":370},"425",[255,438],{"x":292,"y":422,"width":293,"height":301,"rx":258,"fill":261,"stroke":269,"style":270},[276,440,441],{"x":296,"y":425,"style":337},"errors ignored",[40,443,445],{"id":444},"step-2-read-the-sheet-with-the-information-subtotal-uses","Step 2 — Read the sheet with the information SUBTOTAL uses",[45,447,451],{"className":448,"code":449,"language":450,"meta":50,"style":50},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\nfrom openpyxl import load_workbook\n\nwb = load_workbook(\"sales_subtotals.xlsx\")                       # formulas, not values\nws = wb[\"Sales\"]\nrows = []\nfor r in range(2, ws.max_row + 1):\n    amount = ws.cell(r, 3).value\n    rows.append({\"row\": r, \"Region\": ws.cell(r, 1).value, \"Rep\": ws.cell(r, 2).value,\n                 \"Amount\": amount if isinstance(amount, (int, float)) else None,\n                 \"is_subtotal\": isinstance(amount, str) and amount.upper().startswith((\"=SUBTOTAL(\", \"=_XLFN.AGGREGATE(\")),\n                 \"hidden\": bool(ws.row_dimensions[r].hidden)})\nsales = pd.DataFrame(rows)\nprint(sales.to_string(index=False))\n","python",[14,452,453,468,481,487,507,524,535,567,583,616,654,692,706,717],{"__ignoreMap":50},[54,454,455,458,462,465],{"class":56,"line":57},[54,456,457],{"class":119},"import",[54,459,461],{"class":460},"skGVy"," pandas ",[54,463,464],{"class":119},"as",[54,466,467],{"class":460}," pd\n",[54,469,470,473,476,478],{"class":56,"line":129},[54,471,472],{"class":119},"from",[54,474,475],{"class":460}," openpyxl ",[54,477,457],{"class":119},[54,479,480],{"class":460}," load_workbook\n",[54,482,483],{"class":56,"line":153},[54,484,486],{"emptyLinePlaceholder":485},true,"\n",[54,488,489,492,495,498,501,504],{"class":56,"line":173},[54,490,491],{"class":460},"wb ",[54,493,494],{"class":119},"=",[54,496,497],{"class":460}," load_workbook(",[54,499,500],{"class":64},"\"sales_subtotals.xlsx\"",[54,502,503],{"class":460},")                       ",[54,505,506],{"class":200},"# formulas, not values\n",[54,508,510,513,515,518,521],{"class":56,"line":509},5,[54,511,512],{"class":460},"ws ",[54,514,494],{"class":119},[54,516,517],{"class":460}," wb[",[54,519,520],{"class":64},"\"Sales\"",[54,522,523],{"class":460},"]\n",[54,525,527,530,532],{"class":56,"line":526},6,[54,528,529],{"class":460},"rows ",[54,531,494],{"class":119},[54,533,534],{"class":460}," []\n",[54,536,538,541,544,547,550,553,555,558,561,564],{"class":56,"line":537},7,[54,539,540],{"class":119},"for",[54,542,543],{"class":460}," r ",[54,545,546],{"class":119},"in",[54,548,549],{"class":106}," range",[54,551,552],{"class":460},"(",[54,554,332],{"class":106},[54,556,557],{"class":460},", ws.max_row ",[54,559,560],{"class":119},"+",[54,562,563],{"class":106}," 1",[54,565,566],{"class":460},"):\n",[54,568,570,573,575,578,580],{"class":56,"line":569},8,[54,571,572],{"class":460},"    amount ",[54,574,494],{"class":119},[54,576,577],{"class":460}," ws.cell(r, ",[54,579,358],{"class":106},[54,581,582],{"class":460},").value\n",[54,584,586,589,592,595,598,601,603,606,609,611,613],{"class":56,"line":585},9,[54,587,588],{"class":460},"    rows.append({",[54,590,591],{"class":64},"\"row\"",[54,593,594],{"class":460},": r, ",[54,596,597],{"class":64},"\"Region\"",[54,599,600],{"class":460},": ws.cell(r, ",[54,602,306],{"class":106},[54,604,605],{"class":460},").value, ",[54,607,608],{"class":64},"\"Rep\"",[54,610,600],{"class":460},[54,612,332],{"class":106},[54,614,615],{"class":460},").value,\n",[54,617,619,622,625,628,631,634,637,639,642,645,648,651],{"class":56,"line":618},10,[54,620,621],{"class":64},"                 \"Amount\"",[54,623,624],{"class":460},": amount ",[54,626,627],{"class":119},"if",[54,629,630],{"class":106}," isinstance",[54,632,633],{"class":460},"(amount, (",[54,635,636],{"class":106},"int",[54,638,225],{"class":460},[54,640,641],{"class":106},"float",[54,643,644],{"class":460},")) ",[54,646,647],{"class":119},"else",[54,649,650],{"class":106}," None",[54,652,653],{"class":460},",\n",[54,655,657,660,663,666,669,672,675,678,681,684,686,689],{"class":56,"line":656},11,[54,658,659],{"class":64},"                 \"is_subtotal\"",[54,661,662],{"class":460},": ",[54,664,665],{"class":106},"isinstance",[54,667,668],{"class":460},"(amount, ",[54,670,671],{"class":106},"str",[54,673,674],{"class":460},") ",[54,676,677],{"class":119},"and",[54,679,680],{"class":460}," amount.upper().startswith((",[54,682,683],{"class":64},"\"=SUBTOTAL(\"",[54,685,225],{"class":460},[54,687,688],{"class":64},"\"=_XLFN.AGGREGATE(\"",[54,690,691],{"class":460},")),\n",[54,693,695,698,700,703],{"class":56,"line":694},12,[54,696,697],{"class":64},"                 \"hidden\"",[54,699,662],{"class":460},[54,701,702],{"class":106},"bool",[54,704,705],{"class":460},"(ws.row_dimensions[r].hidden)})\n",[54,707,709,712,714],{"class":56,"line":708},13,[54,710,711],{"class":460},"sales ",[54,713,494],{"class":119},[54,715,716],{"class":460}," pd.DataFrame(rows)\n",[54,718,720,723,726,730,732,735],{"class":56,"line":719},14,[54,721,722],{"class":106},"print",[54,724,725],{"class":460},"(sales.to_string(",[54,727,729],{"class":728},"sa561","index",[54,731,494],{"class":119},[54,733,734],{"class":106},"False",[54,736,737],{"class":460},"))\n",[10,739,740,742,743,745,746,748,749,752,753,756,757,760,761,764,765,768],{},[14,741,16],{}," decides what to skip from two facts that a values-only read throws away: whether a cell contains a ",[14,744,16],{}," or ",[14,747,23],{}," formula, and whether its row is hidden. openpyxl, loaded without ",[14,750,751],{},"data_only",", still has both — the formula text in the cell and the ",[14,754,755],{},"hidden"," flag on the row dimension — so they become two boolean columns next to the data. Recognising subtotal rows by their formula is more reliable than looking for the word ",[76,758,759],{},"total"," in a label, which catches a rep called ",[76,762,763],{},"Totalcare"," and misses a row labelled ",[76,766,767],{},"Sum North",". The subtotal rows' amounts are left empty, since the point is to recalculate them, not to trust them. The printed table shows the result: three subtotal rows flagged, and row 3 — Ben — marked as hidden.",[40,770,772],{"id":771},"step-3-subtotal-in-pandas","Step 3 — SUBTOTAL in pandas",[45,774,776],{"className":448,"code":775,"language":450,"meta":50,"style":50},"detail = sales[~sales[\"is_subtotal\"]]\nvisible = detail[~detail[\"hidden\"]]\n\nnaive = pd.read_excel(\"calculated\u002Fsales_subtotals.xlsx\", sheet_name=\"Sales\", usecols=\"A:C\").iloc[:7]\nresults = {\n    \"naive sum of C2:C8\": naive[\"Amount\"].sum(),                 # what summing the column in pandas gives\n    \"SUBTOTAL(9)  sum\": detail[\"Amount\"].sum(),\n    \"SUBTOTAL(109) sum\": visible[\"Amount\"].sum(),\n    \"SUBTOTAL(1)  average\": detail[\"Amount\"].mean(),\n    \"SUBTOTAL(101) average\": visible[\"Amount\"].mean(),\n    \"SUBTOTAL(4)  max\": detail[\"Amount\"].max(),\n}\nfor k, v in results.items():\n    print(f\"{k:24} {v:>10,.2f}\")\nprint(detail.groupby(\"Region\")[\"Amount\"].sum().to_dict())      # the regional SUBTOTAL rows, rebuilt\n",[14,777,778,800,820,824,864,874,891,904,916,928,939,951,956,968,1010],{"__ignoreMap":50},[54,779,780,783,785,788,791,794,797],{"class":56,"line":57},[54,781,782],{"class":460},"detail ",[54,784,494],{"class":119},[54,786,787],{"class":460}," sales[",[54,789,790],{"class":119},"~",[54,792,793],{"class":460},"sales[",[54,795,796],{"class":64},"\"is_subtotal\"",[54,798,799],{"class":460},"]]\n",[54,801,802,805,807,810,812,815,818],{"class":56,"line":129},[54,803,804],{"class":460},"visible ",[54,806,494],{"class":119},[54,808,809],{"class":460}," detail[",[54,811,790],{"class":119},[54,813,814],{"class":460},"detail[",[54,816,817],{"class":64},"\"hidden\"",[54,819,799],{"class":460},[54,821,822],{"class":56,"line":153},[54,823,486],{"emptyLinePlaceholder":485},[54,825,826,829,831,834,837,839,842,844,846,848,851,853,856,859,862],{"class":56,"line":173},[54,827,828],{"class":460},"naive ",[54,830,494],{"class":119},[54,832,833],{"class":460}," pd.read_excel(",[54,835,836],{"class":64},"\"calculated\u002Fsales_subtotals.xlsx\"",[54,838,225],{"class":460},[54,840,841],{"class":728},"sheet_name",[54,843,494],{"class":119},[54,845,520],{"class":64},[54,847,225],{"class":460},[54,849,850],{"class":728},"usecols",[54,852,494],{"class":119},[54,854,855],{"class":64},"\"A:C\"",[54,857,858],{"class":460},").iloc[:",[54,860,861],{"class":106},"7",[54,863,523],{"class":460},[54,865,866,869,871],{"class":56,"line":509},[54,867,868],{"class":460},"results ",[54,870,494],{"class":119},[54,872,873],{"class":460}," {\n",[54,875,876,879,882,885,888],{"class":56,"line":526},[54,877,878],{"class":64},"    \"naive sum of C2:C8\"",[54,880,881],{"class":460},": naive[",[54,883,884],{"class":64},"\"Amount\"",[54,886,887],{"class":460},"].sum(),                 ",[54,889,890],{"class":200},"# what summing the column in pandas gives\n",[54,892,893,896,899,901],{"class":56,"line":537},[54,894,895],{"class":64},"    \"SUBTOTAL(9)  sum\"",[54,897,898],{"class":460},": detail[",[54,900,884],{"class":64},[54,902,903],{"class":460},"].sum(),\n",[54,905,906,909,912,914],{"class":56,"line":569},[54,907,908],{"class":64},"    \"SUBTOTAL(109) sum\"",[54,910,911],{"class":460},": visible[",[54,913,884],{"class":64},[54,915,903],{"class":460},[54,917,918,921,923,925],{"class":56,"line":585},[54,919,920],{"class":64},"    \"SUBTOTAL(1)  average\"",[54,922,898],{"class":460},[54,924,884],{"class":64},[54,926,927],{"class":460},"].mean(),\n",[54,929,930,933,935,937],{"class":56,"line":618},[54,931,932],{"class":64},"    \"SUBTOTAL(101) average\"",[54,934,911],{"class":460},[54,936,884],{"class":64},[54,938,927],{"class":460},[54,940,941,944,946,948],{"class":56,"line":656},[54,942,943],{"class":64},"    \"SUBTOTAL(4)  max\"",[54,945,898],{"class":460},[54,947,884],{"class":64},[54,949,950],{"class":460},"].max(),\n",[54,952,953],{"class":56,"line":694},[54,954,955],{"class":460},"}\n",[54,957,958,960,963,965],{"class":56,"line":708},[54,959,540],{"class":119},[54,961,962],{"class":460}," k, v ",[54,964,546],{"class":119},[54,966,967],{"class":460}," results.items():\n",[54,969,970,973,975,978,981,985,988,991,994,997,1000,1003,1005,1007],{"class":56,"line":719},[54,971,972],{"class":106},"    print",[54,974,552],{"class":460},[54,976,977],{"class":119},"f",[54,979,980],{"class":64},"\"",[54,982,984],{"class":983},"sSjpA","{",[54,986,987],{"class":460},"k",[54,989,990],{"class":119},":24",[54,992,993],{"class":983},"}",[54,995,996],{"class":983}," {",[54,998,999],{"class":460},"v",[54,1001,1002],{"class":119},":>10,.2f",[54,1004,993],{"class":983},[54,1006,980],{"class":64},[54,1008,1009],{"class":460},")\n",[54,1011,1013,1015,1018,1020,1023,1025,1028],{"class":56,"line":1012},15,[54,1014,722],{"class":106},[54,1016,1017],{"class":460},"(detail.groupby(",[54,1019,597],{"class":64},[54,1021,1022],{"class":460},")[",[54,1024,884],{"class":64},[54,1026,1027],{"class":460},"].sum().to_dict())      ",[54,1029,1030],{"class":200},"# the regional SUBTOTAL rows, rebuilt\n",[10,1032,1033,1034,1036,1037,1040,1041,1044,1045,1048,1049,1052,1053,1056,1057,1060,1061,1064,1065,1068,1069,1071],{},"With the two flags in place, every ",[14,1035,16],{}," variant is a filter followed by an ordinary pandas aggregation. Function numbers 1 to 11 include hidden rows: drop the subtotal rows and aggregate. Numbers 101 to 111 ignore hidden rows: drop those too. Each number maps to one pandas method — 1 is ",[14,1038,1039],{},"mean",", 2 ",[14,1042,1043],{},"count",", 3 the count of non-blank cells, 4 ",[14,1046,1047],{},"max",", 5 ",[14,1050,1051],{},"min",", 6 ",[14,1054,1055],{},"prod",", 7 and 8 the sample and population standard deviations, 9 ",[14,1058,1059],{},"sum",", 10 and 11 the two variances. The results match LibreOffice to the cent: 3,990 and 3,190 for the sums, 798 and 797.5 for the averages, 1,200 for the maximum. The regional subtotals come back with a ",[14,1062,1063],{},"groupby"," — 2,000 for North and 1,990 for South — without ever reading the stored subtotal cells. The first line is the trap this page is about: reading the calculated workbook with ",[14,1066,1067],{},"read_excel"," and summing the column gives 7,980, exactly the double count of Excel's plain ",[14,1070,210],{},", because the regional subtotals are ordinary numbers once the formulas are gone.",[236,1073,245,1079,245,1082,245,1085,245,1089,245,1096,245,1102,245,1107,245,1112,245,1117,245,1120,245,1126,245,1129,245,1133,245,1137,245,1140,245,1145,245,1150,245,1153,245,1157,245,1160,245,1163,245,1168],{"viewBox":1074,"role":239,"ariaLabelledBy":1075,"xmlns":243,"style":1078},"4 0 752 108",[1076,1077],"sub-filter-t","sub-filter-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[247,1080,1081],{"id":1076},"SUBTOTAL as filters in pandas",[251,1083,1084],{"id":1077},"Start with all rows. Removing rows whose formula is SUBTOTAL or AGGREGATE reproduces function numbers 1 to 11. Also removing hidden rows reproduces 101 to 111. Then apply the matching pandas method.",[255,1086],{"x":383,"y":258,"width":1087,"height":1088,"fill":261},"752","108",[255,1090],{"x":1091,"y":265,"width":1092,"height":1093,"rx":1094,"fill":268,"stroke":269,"style":1095},"20.0","153.0","76.5","12","stroke-width:2px",[276,1097,1101],{"x":1098,"y":1099,"style":1100},"96.5","50.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","All rows",[276,1103,1106],{"x":1098,"y":1104,"style":1105},"69.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","as read by openpyxl",[56,1108],{"x1":1109,"y1":1110,"x2":1111,"y2":1110,"stroke":310,"style":1095},"178.0","54.2","200.0",[1113,1114],"polygon",{"points":1115,"fill":1116},"204.0,54.2 195.0,59.2 195.0,49.2","#5b5cf0",[255,1118],{"x":1119,"y":265,"width":1092,"height":1093,"rx":1094,"fill":309,"stroke":310,"style":1095},"209.0",[276,1121,1125],{"x":1122,"y":1123,"style":1124},"285.5","41.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Drop subtotal",[276,1127,1128],{"x":1122,"y":305,"style":1124},"rows",[276,1130,1132],{"x":1122,"y":1131,"style":1105},"78.5","= SUBTOTAL 1–11",[56,1134],{"x1":1135,"y1":1110,"x2":1136,"y2":1110,"stroke":310,"style":1095},"367.0","389.0",[1113,1138],{"points":1139,"fill":1116},"393.0,54.2 384.0,59.2 384.0,49.2",[255,1141],{"x":1142,"y":265,"width":1092,"height":1093,"rx":1094,"fill":1143,"stroke":1144,"style":1095},"398.0","#fdefd8","var(--gold,#b4740a)",[276,1146,1149],{"x":1147,"y":1099,"style":1148},"474.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Drop hidden rows",[276,1151,1152],{"x":1147,"y":1104,"style":1105},"= SUBTOTAL 101–111",[56,1154],{"x1":1155,"y1":1110,"x2":1156,"y2":1110,"stroke":310,"style":1095},"556.0","578.0",[1113,1158],{"points":1159,"fill":1116},"582.0,54.2 573.0,59.2 573.0,49.2",[255,1161],{"x":1162,"y":265,"width":1092,"height":1093,"rx":1094,"fill":366,"stroke":367,"style":1095},"587.0",[276,1164,1167],{"x":1165,"y":1099,"style":1166},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","pandas method",[276,1169,1170],{"x":1165,"y":1104,"style":1105},"sum, mean, max…",[40,1172,1174],{"id":1173},"step-4-aggregate-ignore-the-errors","Step 4 — AGGREGATE: ignore the errors",[45,1176,1178],{"className":448,"code":1177,"language":450,"meta":50,"style":50},"raw = [c.value for c in load_workbook(\"calculated\u002Fsales_subtotals.xlsx\", data_only=True)[\"Meters\"][\"B\"][1:6]]\nprint(\"openpyxl sees:\", raw)                                      # error values as text\nmeters = pd.read_excel(\"calculated\u002Fsales_subtotals.xlsx\", sheet_name=\"Meters\", usecols=\"A:B\")\nprint(\"read_excel sees:\", meters[\"Reading\"].tolist())             # error values already NaN\nreadings = pd.to_numeric(pd.Series(raw), errors=\"coerce\")         # the explicit route from openpyxl values\nprint({\"AGGREGATE(9,6) sum\": readings.sum(), \"AGGREGATE(1,6) average\": round(readings.mean(), 2),\n       \"AGGREGATE(14,6,k=2) 2nd largest\": readings.nlargest(2).iloc[-1], \"AGGREGATE(16,6,0.5) median\": readings.quantile(0.5)})\n",[14,1179,1180,1232,1247,1277,1298,1322,1351],{"__ignoreMap":50},[54,1181,1182,1185,1187,1190,1192,1195,1197,1199,1201,1203,1205,1207,1210,1212,1215,1218,1221,1223,1225,1228,1230],{"class":56,"line":57},[54,1183,1184],{"class":460},"raw ",[54,1186,494],{"class":119},[54,1188,1189],{"class":460}," [c.value ",[54,1191,540],{"class":119},[54,1193,1194],{"class":460}," c ",[54,1196,546],{"class":119},[54,1198,497],{"class":460},[54,1200,836],{"class":64},[54,1202,225],{"class":460},[54,1204,751],{"class":728},[54,1206,494],{"class":119},[54,1208,1209],{"class":106},"True",[54,1211,1022],{"class":460},[54,1213,1214],{"class":64},"\"Meters\"",[54,1216,1217],{"class":460},"][",[54,1219,1220],{"class":64},"\"B\"",[54,1222,1217],{"class":460},[54,1224,306],{"class":106},[54,1226,1227],{"class":460},":",[54,1229,426],{"class":106},[54,1231,799],{"class":460},[54,1233,1234,1236,1238,1241,1244],{"class":56,"line":129},[54,1235,722],{"class":106},[54,1237,552],{"class":460},[54,1239,1240],{"class":64},"\"openpyxl sees:\"",[54,1242,1243],{"class":460},", raw)                                      ",[54,1245,1246],{"class":200},"# error values as text\n",[54,1248,1249,1252,1254,1256,1258,1260,1262,1264,1266,1268,1270,1272,1275],{"class":56,"line":153},[54,1250,1251],{"class":460},"meters ",[54,1253,494],{"class":119},[54,1255,833],{"class":460},[54,1257,836],{"class":64},[54,1259,225],{"class":460},[54,1261,841],{"class":728},[54,1263,494],{"class":119},[54,1265,1214],{"class":64},[54,1267,225],{"class":460},[54,1269,850],{"class":728},[54,1271,494],{"class":119},[54,1273,1274],{"class":64},"\"A:B\"",[54,1276,1009],{"class":460},[54,1278,1279,1281,1283,1286,1289,1292,1295],{"class":56,"line":173},[54,1280,722],{"class":106},[54,1282,552],{"class":460},[54,1284,1285],{"class":64},"\"read_excel sees:\"",[54,1287,1288],{"class":460},", meters[",[54,1290,1291],{"class":64},"\"Reading\"",[54,1293,1294],{"class":460},"].tolist())             ",[54,1296,1297],{"class":200},"# error values already NaN\n",[54,1299,1300,1303,1305,1308,1311,1313,1316,1319],{"class":56,"line":509},[54,1301,1302],{"class":460},"readings ",[54,1304,494],{"class":119},[54,1306,1307],{"class":460}," pd.to_numeric(pd.Series(raw), ",[54,1309,1310],{"class":728},"errors",[54,1312,494],{"class":119},[54,1314,1315],{"class":64},"\"coerce\"",[54,1317,1318],{"class":460},")         ",[54,1320,1321],{"class":200},"# the explicit route from openpyxl values\n",[54,1323,1324,1326,1329,1332,1335,1338,1340,1343,1346,1348],{"class":56,"line":526},[54,1325,722],{"class":106},[54,1327,1328],{"class":460},"({",[54,1330,1331],{"class":64},"\"AGGREGATE(9,6) sum\"",[54,1333,1334],{"class":460},": readings.sum(), ",[54,1336,1337],{"class":64},"\"AGGREGATE(1,6) average\"",[54,1339,662],{"class":460},[54,1341,1342],{"class":106},"round",[54,1344,1345],{"class":460},"(readings.mean(), ",[54,1347,332],{"class":106},[54,1349,1350],{"class":460},"),\n",[54,1352,1353,1356,1359,1361,1364,1367,1369,1372,1375,1378,1381],{"class":56,"line":537},[54,1354,1355],{"class":64},"       \"AGGREGATE(14,6,k=2) 2nd largest\"",[54,1357,1358],{"class":460},": readings.nlargest(",[54,1360,332],{"class":106},[54,1362,1363],{"class":460},").iloc[",[54,1365,1366],{"class":119},"-",[54,1368,306],{"class":106},[54,1370,1371],{"class":460},"], ",[54,1373,1374],{"class":64},"\"AGGREGATE(16,6,0.5) median\"",[54,1376,1377],{"class":460},": readings.quantile(",[54,1379,1380],{"class":106},"0.5",[54,1382,1383],{"class":460},")})\n",[10,1385,1386,1387,28,1390,1393,1394,1396,1397,1400,1401,1403,1404,1406,1407,225,1409,225,1411,28,1414,1417,1418,1421,1422,1425,1426,1428,1429,1431,1432,28,1434,1437,1438,1440,1441,38],{},"The two reads show something worth knowing. openpyxl returns the stored error values as text — ",[14,1388,1389],{},"'#DIV\u002F0!'",[14,1391,1392],{},"'#N\u002FA'"," — while ",[14,1395,1067],{}," quietly turns them into ",[14,1398,1399],{},"NaN"," before you see them. So pandas, by default, already behaves like ",[14,1402,23],{}," option 6 rather than like Excel's ",[14,1405,210],{},": errors vanish and ",[14,1408,1059],{},[14,1410,1039],{},[14,1412,1413],{},"median",[14,1415,1416],{},"nlargest"," skip them. That is convenient, and also a risk, because a column full of ",[14,1419,1420],{},"#REF!"," errors produces a perfectly plausible total from the few cells that still work. When errors matter, read the values with openpyxl, count the error strings, and then coerce them with ",[14,1423,1424],{},"pd.to_numeric(errors=\"coerce\")"," as the code does. The four results match LibreOffice — a sum of 425, an average of 141.67, a second-largest reading of 120 and a median of 120. The function numbers map onto pandas in the same way as for ",[14,1427,16],{},", with the extras ",[14,1430,23],{}," adds: 12 is the median, 13 the mode, 14 and 15 ",[14,1433,1416],{},[14,1435,1436],{},"nsmallest"," with ",[76,1439,987],{},", and 16 to 19 percentiles and quartiles via ",[14,1442,1443],{},"quantile",[10,1445,1446,1447,1450],{},"A quick way to surface errors before they disappear is to count them per column straight from the openpyxl values — ",[14,1448,1449],{},"sum(isinstance(v, str) and v.startswith(\"#\") for v in raw)"," — and stop or warn when the count is not zero. In a monthly report that reads a workbook maintained by hand, a sudden jump in error cells is usually the first sign that someone has deleted a sheet or a column the formulas depended on.",[40,1452,1454],{"id":1453},"the-aggregate-options-translated","The AGGREGATE options, translated",[10,1456,1457,1459,1460,1463,1464,1467,1468,1470],{},[14,1458,23],{},"'s second argument combines three switches: ignore nested subtotals, ignore hidden rows, ignore errors. Option 6 — errors only — is the most used, but it does ",[76,1461,1462],{},"not"," skip nested subtotals, which surprises people: an ",[14,1465,1466],{},"AGGREGATE(14, 6, …)"," asking for the largest value in a column that contains subtotal rows will happily return a subtotal. Options 0 to 3 skip nested subtotals; 1, 3, 5 and 7 skip hidden rows; 2, 3, 6 and 7 skip errors. In pandas each switch is one of the filters above — drop the subtotal rows, drop the hidden rows, coerce errors to ",[14,1469,1399],{}," — so any option is a combination of the three.",[236,1472,245,1477,245,1480,245,1483,245,1486,245,1491,245,1497,245,1502,245,1506,245,1510,245,1514,245,1516,245,1521,245,1525,245,1528,245,1531],{"viewBox":1473,"role":239,"ariaLabelledBy":1474,"xmlns":243,"style":1078},"4 0 752 173",[1475,1476],"sub-options-t","sub-options-d",[247,1478,1479],{"id":1475},"Three switches behind every option",[251,1481,1482],{"id":1476},"AGGREGATE options combine ignoring nested subtotals, ignoring hidden rows and ignoring errors. In pandas each switch is a filter — drop subtotal rows, drop hidden rows, coerce errors to NaN. Option 6 ignores errors only and still includes nested subtotals.",[255,1484],{"x":383,"y":258,"width":1087,"height":1485,"fill":261},"173",[255,1487],{"x":1091,"y":265,"width":1488,"height":1489,"rx":1490,"fill":309,"stroke":310,"style":1095},"351.0","141.0","14",[276,1492,1496],{"x":1493,"y":1494,"style":1495},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Excel switch",[56,1498],{"x1":1499,"y1":1500,"x2":1501,"y2":1500,"stroke":310,"style":270},"36.0","57.0","355.0",[276,1503,1505],{"x":1493,"y":1504,"style":337},"77.0","nested subtotals: options 0–3",[276,1507,1509],{"x":1493,"y":1508,"style":337},"103.0","hidden rows: 1, 3, 5, 7",[276,1511,1513],{"x":1493,"y":1512,"style":337},"129.0","errors: 2, 3, 6, 7",[255,1515],{"x":1136,"y":265,"width":1488,"height":1489,"rx":1490,"fill":366,"stroke":367,"style":1095},[276,1517,1520],{"x":1518,"y":1494,"style":1519},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","pandas filter",[56,1522],{"x1":1523,"y1":1500,"x2":1524,"y2":1500,"stroke":367,"style":270},"405.0","724.0",[276,1526,1527],{"x":1518,"y":1504,"style":337},"drop is_subtotal rows",[276,1529,1530],{"x":1518,"y":1508,"style":337},"drop hidden rows",[276,1532,1533],{"x":1518,"y":1512,"style":337},"to_numeric(errors='coerce')",[40,1535,1537],{"id":1536},"common-pitfalls","Common pitfalls",[1539,1540,1541,1549,1555,1567,1576],"ul",{},[1542,1543,1544,1548],"li",{},[1545,1546,1547],"strong",{},"Summing a column that contains subtotal rows."," pandas sees plain numbers; drop those rows first.",[1542,1550,1551,1554],{},[1545,1552,1553],{},"Detecting subtotals by label."," Check the formula with openpyxl instead.",[1542,1556,1557,1560,1561,28,1564,1566],{},[1545,1558,1559],{},"Reading values only."," ",[14,1562,1563],{},"data_only=True",[14,1565,1067],{}," lose formulas and hidden-row flags.",[1542,1568,1569,1572,1573,1575],{},[1545,1570,1571],{},"Errors as text."," Coerce with ",[14,1574,1424],{}," before aggregating.",[1542,1577,1578,1581],{},[1545,1579,1580],{},"Option 6 and nested subtotals."," It ignores errors but still counts subtotal rows.",[40,1583,1585],{"id":1584},"conclusion","Conclusion",[10,1587,1588,28,1590,1592,1593,745,1595,1597,1598,1600],{},[14,1589,16],{},[14,1591,23],{}," are filters plus an aggregation. Read the sheet with openpyxl to flag rows whose formula is a ",[14,1594,16],{},[14,1596,23],{}," and rows that are hidden, drop the flagged rows the function number says to skip, coerce error values to ",[14,1599,1399],{},", and apply the matching pandas method. Recalculating the original workbook in LibreOffice gives the numbers to check against.",[40,1602,1604],{"id":1603},"frequently-asked-questions","Frequently asked questions",[10,1606,1607,1610,1611,1614],{},[1545,1608,1609],{},"What is the pandas equivalent of SUBTOTAL(9, …)?","\nDrop the rows that contain SUBTOTAL formulas and sum the rest; use ",[14,1612,1613],{},"groupby(...).sum()"," to rebuild the subtotals themselves.",[10,1616,1617,1620],{},[1545,1618,1619],{},"What is the difference between SUBTOTAL 9 and 109?","\nBoth skip nested subtotals; 109 also ignores rows that have been hidden manually.",[10,1622,1623,1626,1627,1629],{},[1545,1624,1625],{},"How do I ignore #DIV\u002F0! and #N\u002FA when summing in pandas?","\nConvert the column with ",[14,1628,1424],{},", which turns error text into NaN, then sum; pandas skips NaN by default.",[10,1631,1632,1635,1636,1639],{},[1545,1633,1634],{},"How do I find hidden rows in an Excel file with Python?","\nLoad it with openpyxl and check ",[14,1637,1638],{},"ws.row_dimensions[row].hidden"," for each row.",[40,1641,1643],{"id":1642},"related","Related",[1539,1645,1646,1652,1659,1666],{},[1542,1647,1648,1649,1651],{},"Up one level: ",[34,1650,37],{"href":36}," — Excel functions in pandas.",[1542,1653,1654,1658],{},[34,1655,1657],{"href":1656},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-subtotals-and-grand-totals-to-a-pandas-pivot-table\u002F","Add Subtotals and Grand Totals to a pandas Pivot Table"," — building subtotals in pandas.",[1542,1660,1661,1665],{},[34,1662,1664],{"href":1663},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fiferror-and-isna-equivalents-in-pandas\u002F","IFERROR and ISNA Equivalents in pandas"," — error handling.",[1542,1667,1668,1672],{},[34,1669,1671],{"href":1670},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fskip-hidden-and-filtered-rows-when-reading-excel\u002F","Skip Hidden and Filtered Rows When Reading Excel"," — hidden rows when reading.",[1674,1675,1676],"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 .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":50,"searchDepth":129,"depth":129,"links":1678},[1679,1680,1681,1682,1683,1684,1685,1686,1687,1688],{"id":42,"depth":129,"text":43},{"id":92,"depth":129,"text":93},{"id":444,"depth":129,"text":445},{"id":771,"depth":129,"text":772},{"id":1173,"depth":129,"text":1174},{"id":1453,"depth":129,"text":1454},{"id":1536,"depth":129,"text":1537},{"id":1584,"depth":129,"text":1585},{"id":1603,"depth":129,"text":1604},{"id":1642,"depth":129,"text":1643},"2026-10-02","Reproduce Excel's SUBTOTAL and AGGREGATE in pandas: totals that skip the subtotal rows already in a sheet, the 1–11 versus 101–111 function numbers that include or ignore hidden rows, and AGGREGATE's option to ignore error values — reading which rows are subtotals and which are hidden with openpyxl, and checking every result against LibreOffice.","md",[1693,1695,1697,1699],{"q":1609,"a":1694},"Drop the rows that contain SUBTOTAL formulas and sum the rest; use groupby(...).sum() to rebuild the subtotals themselves.",{"q":1619,"a":1696},"Both skip nested subtotals; 109 also ignores rows that have been hidden manually.",{"q":1625,"a":1698},"Convert the column with pd.to_numeric(errors=\"coerce\"), which turns error text into NaN, then sum; pandas skips NaN by default.",{"q":1634,"a":1700},"Load it with openpyxl and check ws.row_dimensions[row].hidden for each row.","excel subtotal function pandas",null,{"breadcrumb":1704},[1705,1708,1711],{"name":1706,"item":1707},"Home","\u002F",{"name":1709,"item":1710},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":37,"item":36},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas",{"title":5,"description":1714},"Excel SUBTOTAL and AGGREGATE in pandas: skip nested subtotal rows, include or ignore hidden rows (9 vs 109), ignore #DIV\u002F0! and #N\u002FA like AGGREGATE option 6, verified against LibreOffice.","subtotal-and-aggregate-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsubtotal-and-aggregate-equivalents-in-pandas\u002Findex",[1718,1719],"formula-equivalents","pivots-and-summaries","how-to","UJyyeqcyr6VNL1LBBAWX6qaEjUS8oS8EKtGEOCEFNA0",[1723,1727],{"title":1724,"path":1725,"stem":1726,"children":-1},"STDEV.S, STDEV.P, VAR.S and VAR.P in pandas and NumPy","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fstdev-and-var-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fstdev-and-var-equivalents-in-pandas\u002Findex",{"title":1728,"path":1729,"stem":1730,"children":-1},"SUMIF and SUMIFS Equivalent in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fsumif-and-sumifs-equivalent-in-pandas\u002Findex",[1732,1734],{"path":1733,"title":37},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":1735,"title":1709},"\u002Fadvanced-data-transformation-and-cleaning",[],[1738,1741,1744,1747,1750,1753],{"path":1739,"title":1740,"kind":1720},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvlookup-equivalent-in-pandas-for-excel-files","The VLOOKUP Equivalent in pandas for Excel Files",{"path":1742,"title":1743,"kind":1720},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas","Extract Numbers and Codes from Text with pandas",{"path":1745,"title":1746,"kind":1720},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas","Split Full Names into First and Last Names with pandas",{"path":1748,"title":1749,"kind":1720},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"path":1751,"title":1752,"kind":1720},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-percent-of-total-per-group-in-pandas","Calculate Percent of Total per Group in pandas",{"path":1754,"title":1755,"kind":1720},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcount-distinct-values-per-group-in-pandas","Count Distinct Values per Group in pandas",1790972088657]