[{"data":1,"prerenderedAt":2662},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table":2632,"ancestors:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table":2640,"children:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table":2644,"related:\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table":2645},{"id":4,"title":5,"body":6,"dateModified":2598,"datePublished":2598,"description":2599,"extension":2600,"faq":2601,"keyword":2610,"kind":2611,"meta":2612,"navigation":358,"path":2621,"seo":2622,"slug":2625,"stem":2626,"topics":2627,"type":2630,"__hash__":2631},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table\u002Findex.md","Add a Total Row to an Excel Table with Python",{"type":7,"value":8,"toc":2584},"minimark",[9,29,257,262,293,297,304,532,535,539,550,797,832,836,843,947,958,1063,1067,1073,1509,1516,1520,1531,1785,1796,1933,1937,1943,2110,2117,2121,2128,2432,2445,2449,2484,2488,2519,2523,2529,2535,2541,2547,2551,2580],[10,11,12,13,17,18,23,24,28],"p",{},"Excel tables can show a totals row: a last row under the data, styled with the table, where each column has a dropdown to pick Sum, Average, Count, Min, Max and so on. It is better than a hand-written ",[14,15,16],"code",{},"SUM"," under the data because it is part of the table — it moves when rows are added, respects filters and keeps its styling. Adding one from Python takes three pieces that must agree: the table's range, the column definitions and the formulas in the cells. This guide, part of ",[19,20,22],"a",{"href":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002F","Creating Excel Tables and Autofilters with Python",", puts them together with openpyxl and XlsxWriter. Creating the table itself is covered in ",[19,25,27],{"href":26},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","Create an Excel Table with Python",".",[30,31,39,40,39,44,39,48,39,56,39,64,39,68,39,75,39,80,39,85,39,89,39,95,39,99,39,103,39,107,39,111,39,114,39,119,39,124,39,128,39,130,39,133,39,135,39,138,39,141,39,145,39,147,39,151,39,153,39,156,39,158,39,161,39,164,39,168,39,170,39,173,39,175,39,178,39,180,39,183,39,186,39,190,39,192,39,195,39,197,39,200,39,202,39,205,39,208,39,212,39,214,39,217,39,219,39,221,39,223,39,226,39,229,39,233,39,237,39,241,39,243,39,246,39,248,39,251],"svg",{"viewBox":32,"role":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"193 0 374 278","img",[35,36],"tr-sheet-t","tr-sheet-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:374px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[41,42,43],"title",{"id":35},"A sales table with its totals row",[45,46,47],"desc",{"id":36},"A table named Sales has Region, Orders and Amount columns and four data rows. Its totals row reads Total, then 40 orders from SUBTOTAL 109 of the Orders column and 4,360.75 from SUBTOTAL 109 of the Amount column.",[49,50],"rect",{"x":51,"y":52,"width":53,"height":54,"fill":55},"193","0","374","278","#ffffff",[49,57],{"x":58,"y":59,"width":60,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},"241.8","16.0","276.4","28.0","var(--line,#cdd5e6)","stroke-width:1px",[49,65],{"x":58,"y":59,"width":66,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"40.0","#e7ebef",[69,70,74],"text",{"x":71,"y":72,"style":73},"261.8","35.0","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","fx",[69,76,79],{"x":77,"y":72,"style":78},"291.8","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","=SUBTOTAL(109,Sales[Amount])",[49,81],{"x":58,"y":82,"width":83,"height":84,"rx":52,"fill":67,"stroke":62,"style":63},"44.0","32.0","24.0",[49,86],{"x":87,"y":82,"width":88,"height":84,"rx":52,"fill":67,"stroke":62,"style":63},"273.8","66.7",[69,90,94],{"x":91,"y":92,"style":93},"307.2","61.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[49,96],{"x":97,"y":82,"width":98,"height":84,"rx":52,"fill":67,"stroke":62,"style":63},"340.5","66.5",[69,100,102],{"x":101,"y":92,"style":93},"373.8","B",[49,104],{"x":105,"y":82,"width":106,"height":84,"rx":52,"fill":67,"stroke":62,"style":63},"407.0","111.2",[69,108,110],{"x":109,"y":92,"style":93},"462.6","C",[49,112],{"x":58,"y":113,"width":83,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"68.0",[69,115,118],{"x":116,"y":117,"style":93},"257.8","87.0","1",[49,120],{"x":87,"y":113,"width":88,"height":61,"rx":52,"fill":121,"stroke":122,"style":123},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[69,125,127],{"x":91,"y":117,"style":126},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Region",[49,129],{"x":97,"y":113,"width":98,"height":61,"rx":52,"fill":121,"stroke":122,"style":123},[69,131,132],{"x":101,"y":117,"style":126},"Orders",[49,134],{"x":105,"y":113,"width":106,"height":61,"rx":52,"fill":121,"stroke":122,"style":123},[69,136,137],{"x":109,"y":117,"style":126},"Amount",[49,139],{"x":58,"y":140,"width":83,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"96.0",[69,142,144],{"x":116,"y":143,"style":93},"115.0","2",[49,146],{"x":87,"y":140,"width":88,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,148,150],{"x":91,"y":143,"style":149},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","North",[49,152],{"x":97,"y":140,"width":98,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,154,155],{"x":101,"y":143,"style":149},"12",[49,157],{"x":105,"y":140,"width":106,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,159,160],{"x":109,"y":143,"style":149},"1,200.50",[49,162],{"x":58,"y":163,"width":83,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"124.0",[69,165,167],{"x":116,"y":166,"style":93},"143.0","3",[49,169],{"x":87,"y":163,"width":88,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,171,172],{"x":91,"y":166,"style":149},"South",[49,174],{"x":97,"y":163,"width":98,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,176,177],{"x":101,"y":166,"style":149},"8",[49,179],{"x":105,"y":163,"width":106,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,181,182],{"x":109,"y":166,"style":149},"950.00",[49,184],{"x":58,"y":185,"width":83,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"152.0",[69,187,189],{"x":116,"y":188,"style":93},"171.0","4",[49,191],{"x":87,"y":185,"width":88,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,193,194],{"x":91,"y":188,"style":149},"East",[49,196],{"x":97,"y":185,"width":98,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,198,199],{"x":101,"y":188,"style":149},"15",[49,201],{"x":105,"y":185,"width":106,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,203,204],{"x":109,"y":188,"style":149},"1,780.25",[49,206],{"x":58,"y":207,"width":83,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"180.0",[69,209,211],{"x":116,"y":210,"style":93},"199.0","5",[49,213],{"x":87,"y":207,"width":88,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,215,216],{"x":91,"y":210,"style":149},"West",[49,218],{"x":97,"y":207,"width":98,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,220,211],{"x":101,"y":210,"style":149},[49,222],{"x":105,"y":207,"width":106,"height":61,"rx":52,"fill":55,"stroke":62,"style":63},[69,224,225],{"x":109,"y":210,"style":149},"430.00",[49,227],{"x":58,"y":228,"width":83,"height":61,"rx":52,"fill":67,"stroke":62,"style":63},"208.0",[69,230,232],{"x":116,"y":231,"style":93},"227.0","6",[49,234],{"x":87,"y":228,"width":88,"height":61,"rx":52,"fill":235,"stroke":236,"style":123},"#d9f4f1","var(--teal,#0f9488)",[69,238,240],{"x":91,"y":231,"style":239},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Total",[49,242],{"x":97,"y":228,"width":98,"height":61,"rx":52,"fill":235,"stroke":236,"style":123},[69,244,245],{"x":101,"y":231,"style":239},"40",[49,247],{"x":105,"y":228,"width":106,"height":61,"rx":52,"fill":235,"stroke":236,"style":123},[69,249,250],{"x":109,"y":231,"style":239},"4,360.75",[69,252,256],{"x":253,"y":254,"style":255},"380.0","258.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the totals row is part of the table, not a formula under it",[258,259,261],"h2",{"id":260},"prerequisites","Prerequisites",[263,264,269],"pre",{"className":265,"code":266,"language":267,"meta":268,"style":268},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install openpyxl xlsxwriter pandas\n","bash","",[14,270,271],{"__ignoreMap":268},[272,273,276,280,284,287,290],"span",{"class":274,"line":275},"line",1,[272,277,279],{"class":278},"sMTad","pip",[272,281,283],{"class":282},"srMev"," install",[272,285,286],{"class":282}," openpyxl",[272,288,289],{"class":282}," xlsxwriter",[272,291,292],{"class":282}," pandas\n",[258,294,296],{"id":295},"step-1-leave-room-for-the-totals-row-in-the-range","Step 1 — Leave room for the totals row in the range",[10,298,299,300,303],{},"A table's ",[14,301,302],{},"ref"," covers the header, the data and the totals row. With four data rows under a header in row 1, the data ends in row 5 and the totals row is row 6:",[263,305,309],{"className":306,"code":307,"language":308,"meta":268,"style":268},"language-python shiki shiki-themes github-light github-dark-high-contrast","from openpyxl import Workbook\nfrom openpyxl.worksheet.filters import AutoFilter\nfrom openpyxl.worksheet.table import Table, TableColumn, TableStyleInfo\n\nwb = Workbook()\nws = wb.active\nws.title = \"Sales\"\nws.append([\"Region\", \"Orders\", \"Amount\"])\nfor row in [(\"North\", 12, 1200.5), (\"South\", 8, 950), (\"East\", 15, 1780.25), (\"West\", 5, 430)]:\n    ws.append(row)\nlast_data = ws.max_row                   # 5\ntotals_row = last_data + 1               # 6\n","python",[14,310,311,327,340,353,360,372,383,394,417,491,497,512],{"__ignoreMap":268},[272,312,313,317,321,324],{"class":274,"line":275},[272,314,316],{"class":315},"s-kum","from",[272,318,320],{"class":319},"skGVy"," openpyxl ",[272,322,323],{"class":315},"import",[272,325,326],{"class":319}," Workbook\n",[272,328,330,332,335,337],{"class":274,"line":329},2,[272,331,316],{"class":315},[272,333,334],{"class":319}," openpyxl.worksheet.filters ",[272,336,323],{"class":315},[272,338,339],{"class":319}," AutoFilter\n",[272,341,343,345,348,350],{"class":274,"line":342},3,[272,344,316],{"class":315},[272,346,347],{"class":319}," openpyxl.worksheet.table ",[272,349,323],{"class":315},[272,351,352],{"class":319}," Table, TableColumn, TableStyleInfo\n",[272,354,356],{"class":274,"line":355},4,[272,357,359],{"emptyLinePlaceholder":358},true,"\n",[272,361,363,366,369],{"class":274,"line":362},5,[272,364,365],{"class":319},"wb ",[272,367,368],{"class":315},"=",[272,370,371],{"class":319}," Workbook()\n",[272,373,375,378,380],{"class":274,"line":374},6,[272,376,377],{"class":319},"ws ",[272,379,368],{"class":315},[272,381,382],{"class":319}," wb.active\n",[272,384,386,389,391],{"class":274,"line":385},7,[272,387,388],{"class":319},"ws.title ",[272,390,368],{"class":315},[272,392,393],{"class":282}," \"Sales\"\n",[272,395,397,400,403,406,409,411,414],{"class":274,"line":396},8,[272,398,399],{"class":319},"ws.append([",[272,401,402],{"class":282},"\"Region\"",[272,404,405],{"class":319},", ",[272,407,408],{"class":282},"\"Orders\"",[272,410,405],{"class":319},[272,412,413],{"class":282},"\"Amount\"",[272,415,416],{"class":319},"])\n",[272,418,420,423,426,429,432,435,437,440,442,445,448,451,453,455,457,460,462,465,467,469,471,474,476,479,481,483,485,488],{"class":274,"line":419},9,[272,421,422],{"class":315},"for",[272,424,425],{"class":319}," row ",[272,427,428],{"class":315},"in",[272,430,431],{"class":319}," [(",[272,433,434],{"class":282},"\"North\"",[272,436,405],{"class":319},[272,438,155],{"class":439},"sP0c6",[272,441,405],{"class":319},[272,443,444],{"class":439},"1200.5",[272,446,447],{"class":319},"), (",[272,449,450],{"class":282},"\"South\"",[272,452,405],{"class":319},[272,454,177],{"class":439},[272,456,405],{"class":319},[272,458,459],{"class":439},"950",[272,461,447],{"class":319},[272,463,464],{"class":282},"\"East\"",[272,466,405],{"class":319},[272,468,199],{"class":439},[272,470,405],{"class":319},[272,472,473],{"class":439},"1780.25",[272,475,447],{"class":319},[272,477,478],{"class":282},"\"West\"",[272,480,405],{"class":319},[272,482,211],{"class":439},[272,484,405],{"class":319},[272,486,487],{"class":439},"430",[272,489,490],{"class":319},")]:\n",[272,492,494],{"class":274,"line":493},10,[272,495,496],{"class":319},"    ws.append(row)\n",[272,498,500,503,505,508],{"class":274,"line":499},11,[272,501,502],{"class":319},"last_data ",[272,504,368],{"class":315},[272,506,507],{"class":319}," ws.max_row                   ",[272,509,511],{"class":510},"s-wDw","# 5\n",[272,513,515,518,520,523,526,529],{"class":274,"line":514},12,[272,516,517],{"class":319},"totals_row ",[272,519,368],{"class":315},[272,521,522],{"class":319}," last_data ",[272,524,525],{"class":315},"+",[272,527,528],{"class":439}," 1",[272,530,531],{"class":510},"               # 6\n",[10,533,534],{},"The filter buttons, though, belong to the header and data only. Excel sets the table's autofilter range to exclude the totals row, and doing the same avoids a table whose filter includes its own total.",[258,536,538],{"id":537},"step-2-define-the-columns-with-their-totals","Step 2 — Define the columns with their totals",[10,540,541,542,545,546,549],{},"Each ",[14,543,544],{},"TableColumn"," needs the exact header text as its ",[14,547,548],{},"name"," — a mismatch makes Excel repair the file — plus either a label or a function for the totals row. openpyxl can generate columns automatically, but at the point you configure them it only knows placeholder names like \"Column1\", so build them from the header cells:",[263,551,553],{"className":306,"code":552,"language":308,"meta":268,"style":268},"headers = [cell.value for cell in ws[1]]\ncolumns = [TableColumn(id=i, name=name) for i, name in enumerate(headers, start=1)]\ncolumns[0].totalsRowLabel = \"Total\"\ncolumns[1].totalsRowFunction = \"sum\"\ncolumns[2].totalsRowFunction = \"sum\"\n\ntable = Table(displayName=\"Sales\", ref=f\"A1:C{totals_row}\", totalsRowCount=1,\n              tableColumns=columns, autoFilter=AutoFilter(ref=f\"A1:C{last_data}\"))\ntable.tableStyleInfo = TableStyleInfo(name=\"TableStyleMedium2\", showRowStripes=True)\n",[14,554,555,580,629,644,658,670,674,729,767],{"__ignoreMap":268},[272,556,557,560,562,565,567,570,572,575,577],{"class":274,"line":275},[272,558,559],{"class":319},"headers ",[272,561,368],{"class":315},[272,563,564],{"class":319}," [cell.value ",[272,566,422],{"class":315},[272,568,569],{"class":319}," cell ",[272,571,428],{"class":315},[272,573,574],{"class":319}," ws[",[272,576,118],{"class":439},[272,578,579],{"class":319},"]]\n",[272,581,582,585,587,590,594,596,599,601,603,606,608,611,613,616,619,622,624,626],{"class":274,"line":329},[272,583,584],{"class":319},"columns ",[272,586,368],{"class":315},[272,588,589],{"class":319}," [TableColumn(",[272,591,593],{"class":592},"sa561","id",[272,595,368],{"class":315},[272,597,598],{"class":319},"i, ",[272,600,548],{"class":592},[272,602,368],{"class":315},[272,604,605],{"class":319},"name) ",[272,607,422],{"class":315},[272,609,610],{"class":319}," i, name ",[272,612,428],{"class":315},[272,614,615],{"class":439}," enumerate",[272,617,618],{"class":319},"(headers, ",[272,620,621],{"class":592},"start",[272,623,368],{"class":315},[272,625,118],{"class":439},[272,627,628],{"class":319},")]\n",[272,630,631,634,636,639,641],{"class":274,"line":342},[272,632,633],{"class":319},"columns[",[272,635,52],{"class":439},[272,637,638],{"class":319},"].totalsRowLabel ",[272,640,368],{"class":315},[272,642,643],{"class":282}," \"Total\"\n",[272,645,646,648,650,653,655],{"class":274,"line":355},[272,647,633],{"class":319},[272,649,118],{"class":439},[272,651,652],{"class":319},"].totalsRowFunction ",[272,654,368],{"class":315},[272,656,657],{"class":282}," \"sum\"\n",[272,659,660,662,664,666,668],{"class":274,"line":362},[272,661,633],{"class":319},[272,663,144],{"class":439},[272,665,652],{"class":319},[272,667,368],{"class":315},[272,669,657],{"class":282},[272,671,672],{"class":274,"line":374},[272,673,359],{"emptyLinePlaceholder":358},[272,675,676,679,681,684,687,689,692,694,696,698,701,704,708,711,714,717,719,722,724,726],{"class":274,"line":385},[272,677,678],{"class":319},"table ",[272,680,368],{"class":315},[272,682,683],{"class":319}," Table(",[272,685,686],{"class":592},"displayName",[272,688,368],{"class":315},[272,690,691],{"class":282},"\"Sales\"",[272,693,405],{"class":319},[272,695,302],{"class":592},[272,697,368],{"class":315},[272,699,700],{"class":315},"f",[272,702,703],{"class":282},"\"A1:C",[272,705,707],{"class":706},"sSjpA","{",[272,709,710],{"class":319},"totals_row",[272,712,713],{"class":706},"}",[272,715,716],{"class":282},"\"",[272,718,405],{"class":319},[272,720,721],{"class":592},"totalsRowCount",[272,723,368],{"class":315},[272,725,118],{"class":439},[272,727,728],{"class":319},",\n",[272,730,731,734,736,739,742,744,747,749,751,753,755,757,760,762,764],{"class":274,"line":396},[272,732,733],{"class":592},"              tableColumns",[272,735,368],{"class":315},[272,737,738],{"class":319},"columns, ",[272,740,741],{"class":592},"autoFilter",[272,743,368],{"class":315},[272,745,746],{"class":319},"AutoFilter(",[272,748,302],{"class":592},[272,750,368],{"class":315},[272,752,700],{"class":315},[272,754,703],{"class":282},[272,756,707],{"class":706},[272,758,759],{"class":319},"last_data",[272,761,713],{"class":706},[272,763,716],{"class":282},[272,765,766],{"class":319},"))\n",[272,768,769,772,774,777,779,781,784,786,789,791,794],{"class":274,"line":419},[272,770,771],{"class":319},"table.tableStyleInfo ",[272,773,368],{"class":315},[272,775,776],{"class":319}," TableStyleInfo(",[272,778,548],{"class":592},[272,780,368],{"class":315},[272,782,783],{"class":282},"\"TableStyleMedium2\"",[272,785,405],{"class":319},[272,787,788],{"class":592},"showRowStripes",[272,790,368],{"class":315},[272,792,793],{"class":439},"True",[272,795,796],{"class":319},")\n",[10,798,799,802,803,405,806,405,809,405,812,405,815,405,818,405,821,405,824,827,828,831],{},[14,800,801],{},"totalsRowFunction"," accepts ",[14,804,805],{},"sum",[14,807,808],{},"average",[14,810,811],{},"count",[14,813,814],{},"countNums",[14,816,817],{},"max",[14,819,820],{},"min",[14,822,823],{},"stdDev",[14,825,826],{},"var"," and ",[14,829,830],{},"custom",". It is what Excel's dropdown shows as selected; the reader can switch it to another function in Excel afterwards.",[258,833,835],{"id":834},"step-3-write-the-formulas-into-the-totals-row","Step 3 — Write the formulas into the totals row",[10,837,838,839,842],{},"openpyxl records the function but does not write the formula — the cells would stay empty until someone reselects the function in Excel. Write what Excel itself would write: ",[14,840,841],{},"SUBTOTAL"," with function number 109 for a sum, using structured references to the table's columns:",[263,844,846],{"className":306,"code":845,"language":308,"meta":268,"style":268},"ws[f\"A{totals_row}\"] = \"Total\"\nws[f\"B{totals_row}\"] = \"=SUBTOTAL(109,Sales[Orders])\"\nws[f\"C{totals_row}\"] = \"=SUBTOTAL(109,Sales[Amount])\"\nws.add_table(table)\nwb.save(\"sales_table.xlsx\")\nprint(table.ref, table.totalsRowCount)          # A1:C6 1\n",[14,847,848,873,897,921,926,936],{"__ignoreMap":268},[272,849,850,853,855,858,860,862,864,866,869,871],{"class":274,"line":275},[272,851,852],{"class":319},"ws[",[272,854,700],{"class":315},[272,856,857],{"class":282},"\"A",[272,859,707],{"class":706},[272,861,710],{"class":319},[272,863,713],{"class":706},[272,865,716],{"class":282},[272,867,868],{"class":319},"] ",[272,870,368],{"class":315},[272,872,643],{"class":282},[272,874,875,877,879,882,884,886,888,890,892,894],{"class":274,"line":329},[272,876,852],{"class":319},[272,878,700],{"class":315},[272,880,881],{"class":282},"\"B",[272,883,707],{"class":706},[272,885,710],{"class":319},[272,887,713],{"class":706},[272,889,716],{"class":282},[272,891,868],{"class":319},[272,893,368],{"class":315},[272,895,896],{"class":282}," \"=SUBTOTAL(109,Sales[Orders])\"\n",[272,898,899,901,903,906,908,910,912,914,916,918],{"class":274,"line":342},[272,900,852],{"class":319},[272,902,700],{"class":315},[272,904,905],{"class":282},"\"C",[272,907,707],{"class":706},[272,909,710],{"class":319},[272,911,713],{"class":706},[272,913,716],{"class":282},[272,915,868],{"class":319},[272,917,368],{"class":315},[272,919,920],{"class":282}," \"=SUBTOTAL(109,Sales[Amount])\"\n",[272,922,923],{"class":274,"line":355},[272,924,925],{"class":319},"ws.add_table(table)\n",[272,927,928,931,934],{"class":274,"line":362},[272,929,930],{"class":319},"wb.save(",[272,932,933],{"class":282},"\"sales_table.xlsx\"",[272,935,796],{"class":319},[272,937,938,941,944],{"class":274,"line":374},[272,939,940],{"class":439},"print",[272,942,943],{"class":319},"(table.ref, table.totalsRowCount)          ",[272,945,946],{"class":510},"# A1:C6 1\n",[10,948,949,950,953,954,28],{},"Recalculating the saved file in LibreOffice gives 40 orders and 4,360.75. The 100-series function numbers ignore rows hidden by a filter, so when a reader filters to North and South, the totals show 20 and 2,150.50 — the total of what they are looking at. Structured references such as ",[14,951,952],{},"Sales[Amount]"," keep working when rows are added inside the table; they are covered in ",[19,955,957],{"href":956},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fuse-structured-table-references-in-formulas-from-python\u002F","Use Structured Table References in Formulas from Python",[30,959,39,965,39,968,39,971,39,975,39,979,39,984,39,990,39,994,39,998,39,1000,39,1004,39,1007,39,1010,39,1012,39,1016,39,1018,39,1021,39,1023,39,1027,39,1030,39,1032,39,1034,39,1038,39,1041,39,1045,39,1047,39,1051,39,1054,39,1057,39,1059],{"viewBox":960,"role":33,"ariaLabelledBy":961,"xmlns":37,"style":964},"4 0 752 276",[962,963],"tr-functions-t","tr-functions-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[41,966,967],{"id":962},"totalsRowFunction and the formula to write",[45,969,970],{"id":963},"For sum write SUBTOTAL 109, for average SUBTOTAL 101, for count of non-empty cells SUBTOTAL 103, for count of numbers SUBTOTAL 102, for max SUBTOTAL 104 and for min SUBTOTAL 105, each with the table column as a structured reference. A custom function stores its own formula in totalsRowFormula.",[49,972],{"x":189,"y":52,"width":973,"height":974,"fill":55},"752","276",[49,976],{"x":977,"y":59,"width":978,"height":66,"rx":52,"fill":55,"stroke":62,"style":63},"20.0","102.4",[49,980],{"x":981,"y":59,"width":982,"height":66,"rx":52,"fill":121,"stroke":122,"style":983},"122.4","617.6","stroke-width:1.2px",[69,985,989],{"x":986,"y":987,"style":988},"431.2","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Formula in the cell",[49,991],{"x":977,"y":992,"width":978,"height":993,"rx":52,"fill":67,"stroke":62,"style":63},"56.0","34.0",[69,995,805],{"x":83,"y":996,"style":997},"78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[49,999],{"x":981,"y":992,"width":982,"height":993,"rx":52,"fill":55,"stroke":62,"style":63},[69,1001,79],{"x":986,"y":1002,"style":1003},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[49,1005],{"x":977,"y":1006,"width":978,"height":993,"rx":52,"fill":67,"stroke":62,"style":63},"90.0",[69,1008,808],{"x":83,"y":1009,"style":997},"112.0",[49,1011],{"x":981,"y":1006,"width":982,"height":993,"rx":52,"fill":55,"stroke":62,"style":63},[69,1013,1015],{"x":986,"y":1014,"style":1003},"111.0","=SUBTOTAL(101,Sales[Amount])",[49,1017],{"x":977,"y":163,"width":978,"height":993,"rx":52,"fill":67,"stroke":62,"style":63},[69,1019,811],{"x":83,"y":1020,"style":997},"146.0",[49,1022],{"x":981,"y":163,"width":982,"height":993,"rx":52,"fill":55,"stroke":62,"style":63},[69,1024,1026],{"x":986,"y":1025,"style":1003},"145.0","=SUBTOTAL(103,Sales[Region])",[49,1028],{"x":977,"y":1029,"width":978,"height":993,"rx":52,"fill":67,"stroke":62,"style":63},"158.0",[69,1031,814],{"x":83,"y":207,"style":997},[49,1033],{"x":981,"y":1029,"width":982,"height":993,"rx":52,"fill":55,"stroke":62,"style":63},[69,1035,1037],{"x":986,"y":1036,"style":1003},"179.0","=SUBTOTAL(102,Sales[Orders])",[49,1039],{"x":977,"y":1040,"width":978,"height":993,"rx":52,"fill":67,"stroke":62,"style":63},"192.0",[69,1042,1044],{"x":83,"y":1043,"style":997},"214.0","max \u002F min",[49,1046],{"x":981,"y":1040,"width":982,"height":993,"rx":52,"fill":55,"stroke":62,"style":63},[69,1048,1050],{"x":986,"y":1049,"style":1003},"213.0","=SUBTOTAL(104 \u002F 105,…)",[49,1052],{"x":977,"y":1053,"width":978,"height":993,"rx":52,"fill":67,"stroke":62,"style":63},"226.0",[69,1055,830],{"x":83,"y":1056,"style":997},"248.0",[49,1058],{"x":981,"y":1053,"width":982,"height":993,"rx":52,"fill":55,"stroke":62,"style":63},[69,1060,1062],{"x":986,"y":1061,"style":1003},"247.0","totalsRowFormula + same formula",[258,1064,1066],{"id":1065},"step-4-a-custom-total","Step 4 — A custom total",[10,1068,1069,1070,1072],{},"Some totals are not a single function of one column — the average order value, for example, is total amount divided by total orders. Use ",[14,1071,830],{},", store the formula on the column, and write the same formula in the cell:",[263,1074,1076],{"className":306,"code":1075,"language":308,"meta":268,"style":268},"from openpyxl.worksheet.table import TableFormula\n\nwb = Workbook()\nws = wb.active\nws.title = \"Sales\"\nws.append([\"Region\", \"Orders\", \"Amount\", \"Per order\"])\nfor i, (region, n, amount) in enumerate([(\"North\", 12, 1200.5), (\"South\", 8, 950),\n                                          (\"East\", 15, 1780.25), (\"West\", 5, 430)], start=2):\n    ws.append([region, n, amount, f\"=C{i}\u002FB{i}\"])\ncols = [TableColumn(id=i, name=h) for i, h in enumerate([\"Region\", \"Orders\", \"Amount\", \"Per order\"], 1)]\ncols[0].totalsRowLabel = \"Total\"\ncols[1].totalsRowFunction = cols[2].totalsRowFunction = \"sum\"\ncustom = \"SUBTOTAL(109,Sales[Amount])\u002FSUBTOTAL(109,Sales[Orders])\"\ncols[3].totalsRowFunction = \"custom\"\ncols[3].totalsRowFormula = TableFormula(attr_text=custom)\nws.add_table(Table(displayName=\"Sales\", ref=\"A1:D6\", totalsRowCount=1, tableColumns=cols,\n                   autoFilter=AutoFilter(ref=\"A1:D5\")))\nws[\"A6\"], ws[\"B6\"], ws[\"C6\"] = \"Total\", \"=SUBTOTAL(109,Sales[Orders])\", \"=SUBTOTAL(109,Sales[Amount])\"\nws[\"D6\"] = \"=\" + custom\nwb.save(\"sales_custom.xlsx\")\n",[14,1077,1078,1089,1093,1101,1109,1117,1138,1177,1216,1246,1301,1314,1335,1346,1360,1383,1422,1442,1478,1499],{"__ignoreMap":268},[272,1079,1080,1082,1084,1086],{"class":274,"line":275},[272,1081,316],{"class":315},[272,1083,347],{"class":319},[272,1085,323],{"class":315},[272,1087,1088],{"class":319}," TableFormula\n",[272,1090,1091],{"class":274,"line":329},[272,1092,359],{"emptyLinePlaceholder":358},[272,1094,1095,1097,1099],{"class":274,"line":342},[272,1096,365],{"class":319},[272,1098,368],{"class":315},[272,1100,371],{"class":319},[272,1102,1103,1105,1107],{"class":274,"line":355},[272,1104,377],{"class":319},[272,1106,368],{"class":315},[272,1108,382],{"class":319},[272,1110,1111,1113,1115],{"class":274,"line":362},[272,1112,388],{"class":319},[272,1114,368],{"class":315},[272,1116,393],{"class":282},[272,1118,1119,1121,1123,1125,1127,1129,1131,1133,1136],{"class":274,"line":374},[272,1120,399],{"class":319},[272,1122,402],{"class":282},[272,1124,405],{"class":319},[272,1126,408],{"class":282},[272,1128,405],{"class":319},[272,1130,413],{"class":282},[272,1132,405],{"class":319},[272,1134,1135],{"class":282},"\"Per order\"",[272,1137,416],{"class":319},[272,1139,1140,1142,1145,1147,1149,1152,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172,1174],{"class":274,"line":385},[272,1141,422],{"class":315},[272,1143,1144],{"class":319}," i, (region, n, amount) ",[272,1146,428],{"class":315},[272,1148,615],{"class":439},[272,1150,1151],{"class":319},"([(",[272,1153,434],{"class":282},[272,1155,405],{"class":319},[272,1157,155],{"class":439},[272,1159,405],{"class":319},[272,1161,444],{"class":439},[272,1163,447],{"class":319},[272,1165,450],{"class":282},[272,1167,405],{"class":319},[272,1169,177],{"class":439},[272,1171,405],{"class":319},[272,1173,459],{"class":439},[272,1175,1176],{"class":319},"),\n",[272,1178,1179,1182,1184,1186,1188,1190,1192,1194,1196,1198,1200,1202,1204,1207,1209,1211,1213],{"class":274,"line":396},[272,1180,1181],{"class":319},"                                          (",[272,1183,464],{"class":282},[272,1185,405],{"class":319},[272,1187,199],{"class":439},[272,1189,405],{"class":319},[272,1191,473],{"class":439},[272,1193,447],{"class":319},[272,1195,478],{"class":282},[272,1197,405],{"class":319},[272,1199,211],{"class":439},[272,1201,405],{"class":319},[272,1203,487],{"class":439},[272,1205,1206],{"class":319},")], ",[272,1208,621],{"class":592},[272,1210,368],{"class":315},[272,1212,144],{"class":439},[272,1214,1215],{"class":319},"):\n",[272,1217,1218,1221,1223,1226,1228,1231,1233,1236,1238,1240,1242,1244],{"class":274,"line":419},[272,1219,1220],{"class":319},"    ws.append([region, n, amount, ",[272,1222,700],{"class":315},[272,1224,1225],{"class":282},"\"=C",[272,1227,707],{"class":706},[272,1229,1230],{"class":319},"i",[272,1232,713],{"class":706},[272,1234,1235],{"class":282},"\u002FB",[272,1237,707],{"class":706},[272,1239,1230],{"class":319},[272,1241,713],{"class":706},[272,1243,716],{"class":282},[272,1245,416],{"class":319},[272,1247,1248,1251,1253,1255,1257,1259,1261,1263,1265,1268,1270,1273,1275,1277,1280,1282,1284,1286,1288,1290,1292,1294,1297,1299],{"class":274,"line":493},[272,1249,1250],{"class":319},"cols ",[272,1252,368],{"class":315},[272,1254,589],{"class":319},[272,1256,593],{"class":592},[272,1258,368],{"class":315},[272,1260,598],{"class":319},[272,1262,548],{"class":592},[272,1264,368],{"class":315},[272,1266,1267],{"class":319},"h) ",[272,1269,422],{"class":315},[272,1271,1272],{"class":319}," i, h ",[272,1274,428],{"class":315},[272,1276,615],{"class":439},[272,1278,1279],{"class":319},"([",[272,1281,402],{"class":282},[272,1283,405],{"class":319},[272,1285,408],{"class":282},[272,1287,405],{"class":319},[272,1289,413],{"class":282},[272,1291,405],{"class":319},[272,1293,1135],{"class":282},[272,1295,1296],{"class":319},"], ",[272,1298,118],{"class":439},[272,1300,628],{"class":319},[272,1302,1303,1306,1308,1310,1312],{"class":274,"line":499},[272,1304,1305],{"class":319},"cols[",[272,1307,52],{"class":439},[272,1309,638],{"class":319},[272,1311,368],{"class":315},[272,1313,643],{"class":282},[272,1315,1316,1318,1320,1322,1324,1327,1329,1331,1333],{"class":274,"line":514},[272,1317,1305],{"class":319},[272,1319,118],{"class":439},[272,1321,652],{"class":319},[272,1323,368],{"class":315},[272,1325,1326],{"class":319}," cols[",[272,1328,144],{"class":439},[272,1330,652],{"class":319},[272,1332,368],{"class":315},[272,1334,657],{"class":282},[272,1336,1338,1341,1343],{"class":274,"line":1337},13,[272,1339,1340],{"class":319},"custom ",[272,1342,368],{"class":315},[272,1344,1345],{"class":282}," \"SUBTOTAL(109,Sales[Amount])\u002FSUBTOTAL(109,Sales[Orders])\"\n",[272,1347,1349,1351,1353,1355,1357],{"class":274,"line":1348},14,[272,1350,1305],{"class":319},[272,1352,167],{"class":439},[272,1354,652],{"class":319},[272,1356,368],{"class":315},[272,1358,1359],{"class":282}," \"custom\"\n",[272,1361,1363,1365,1367,1370,1372,1375,1378,1380],{"class":274,"line":1362},15,[272,1364,1305],{"class":319},[272,1366,167],{"class":439},[272,1368,1369],{"class":319},"].totalsRowFormula ",[272,1371,368],{"class":315},[272,1373,1374],{"class":319}," TableFormula(",[272,1376,1377],{"class":592},"attr_text",[272,1379,368],{"class":315},[272,1381,1382],{"class":319},"custom)\n",[272,1384,1386,1389,1391,1393,1395,1397,1399,1401,1404,1406,1408,1410,1412,1414,1417,1419],{"class":274,"line":1385},16,[272,1387,1388],{"class":319},"ws.add_table(Table(",[272,1390,686],{"class":592},[272,1392,368],{"class":315},[272,1394,691],{"class":282},[272,1396,405],{"class":319},[272,1398,302],{"class":592},[272,1400,368],{"class":315},[272,1402,1403],{"class":282},"\"A1:D6\"",[272,1405,405],{"class":319},[272,1407,721],{"class":592},[272,1409,368],{"class":315},[272,1411,118],{"class":439},[272,1413,405],{"class":319},[272,1415,1416],{"class":592},"tableColumns",[272,1418,368],{"class":315},[272,1420,1421],{"class":319},"cols,\n",[272,1423,1425,1428,1430,1432,1434,1436,1439],{"class":274,"line":1424},17,[272,1426,1427],{"class":592},"                   autoFilter",[272,1429,368],{"class":315},[272,1431,746],{"class":319},[272,1433,302],{"class":592},[272,1435,368],{"class":315},[272,1437,1438],{"class":282},"\"A1:D5\"",[272,1440,1441],{"class":319},")))\n",[272,1443,1445,1447,1450,1453,1456,1458,1461,1463,1465,1468,1470,1473,1475],{"class":274,"line":1444},18,[272,1446,852],{"class":319},[272,1448,1449],{"class":282},"\"A6\"",[272,1451,1452],{"class":319},"], ws[",[272,1454,1455],{"class":282},"\"B6\"",[272,1457,1452],{"class":319},[272,1459,1460],{"class":282},"\"C6\"",[272,1462,868],{"class":319},[272,1464,368],{"class":315},[272,1466,1467],{"class":282}," \"Total\"",[272,1469,405],{"class":319},[272,1471,1472],{"class":282},"\"=SUBTOTAL(109,Sales[Orders])\"",[272,1474,405],{"class":319},[272,1476,1477],{"class":282},"\"=SUBTOTAL(109,Sales[Amount])\"\n",[272,1479,1481,1483,1486,1488,1490,1493,1496],{"class":274,"line":1480},19,[272,1482,852],{"class":319},[272,1484,1485],{"class":282},"\"D6\"",[272,1487,868],{"class":319},[272,1489,368],{"class":315},[272,1491,1492],{"class":282}," \"=\"",[272,1494,1495],{"class":315}," +",[272,1497,1498],{"class":319}," custom\n",[272,1500,1502,1504,1507],{"class":274,"line":1501},20,[272,1503,930],{"class":319},[272,1505,1506],{"class":282},"\"sales_custom.xlsx\"",[272,1508,796],{"class":319},[10,1510,1511,1512,28],{},"The total per order is 4,360.75 ÷ 40 = 109.02 — a weighted average, which is what the row really means. Averaging the per-order column instead would give each region equal weight regardless of size, the mistake explained in ",[19,1513,1515],{"href":1514},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fuse-different-aggregations-per-column-in-a-pivot\u002F","Use Different Aggregations per Column in a pandas Pivot",[258,1517,1519],{"id":1518},"step-5-the-same-with-xlsxwriter","Step 5 — The same with XlsxWriter",[10,1521,1522,1523,1526,1527,1530],{},"XlsxWriter's ",[14,1524,1525],{},"add_table"," builds the totals row in one step, including the formulas, from a ",[14,1528,1529],{},"total_row"," flag and per-column settings:",[263,1532,1534],{"className":306,"code":1533,"language":308,"meta":268,"style":268},"import xlsxwriter\n\nbook = xlsxwriter.Workbook(\"sales_xw.xlsx\")\nsheet = book.add_worksheet(\"Sales\")\ndata = [[\"North\", 12, 1200.5], [\"South\", 8, 950], [\"East\", 15, 1780.25], [\"West\", 5, 430]]\nsheet.add_table(\"A1:C6\", {\n    \"name\": \"Sales\", \"data\": data, \"total_row\": True,\n    \"columns\": [{\"header\": \"Region\", \"total_string\": \"Total\"},\n                {\"header\": \"Orders\", \"total_function\": \"sum\", \"total_value\": 40},\n                {\"header\": \"Amount\", \"total_function\": \"sum\", \"total_value\": 4360.75}],\n})\nbook.close()\nprint(\"written\")\n",[14,1535,1536,1543,1547,1562,1576,1635,1646,1673,1701,1733,1763,1768,1773],{"__ignoreMap":268},[272,1537,1538,1540],{"class":274,"line":275},[272,1539,323],{"class":315},[272,1541,1542],{"class":319}," xlsxwriter\n",[272,1544,1545],{"class":274,"line":329},[272,1546,359],{"emptyLinePlaceholder":358},[272,1548,1549,1552,1554,1557,1560],{"class":274,"line":342},[272,1550,1551],{"class":319},"book ",[272,1553,368],{"class":315},[272,1555,1556],{"class":319}," xlsxwriter.Workbook(",[272,1558,1559],{"class":282},"\"sales_xw.xlsx\"",[272,1561,796],{"class":319},[272,1563,1564,1567,1569,1572,1574],{"class":274,"line":355},[272,1565,1566],{"class":319},"sheet ",[272,1568,368],{"class":315},[272,1570,1571],{"class":319}," book.add_worksheet(",[272,1573,691],{"class":282},[272,1575,796],{"class":319},[272,1577,1578,1581,1583,1586,1588,1590,1592,1594,1596,1599,1601,1603,1605,1607,1609,1611,1613,1615,1617,1619,1621,1623,1625,1627,1629,1631,1633],{"class":274,"line":362},[272,1579,1580],{"class":319},"data ",[272,1582,368],{"class":315},[272,1584,1585],{"class":319}," [[",[272,1587,434],{"class":282},[272,1589,405],{"class":319},[272,1591,155],{"class":439},[272,1593,405],{"class":319},[272,1595,444],{"class":439},[272,1597,1598],{"class":319},"], [",[272,1600,450],{"class":282},[272,1602,405],{"class":319},[272,1604,177],{"class":439},[272,1606,405],{"class":319},[272,1608,459],{"class":439},[272,1610,1598],{"class":319},[272,1612,464],{"class":282},[272,1614,405],{"class":319},[272,1616,199],{"class":439},[272,1618,405],{"class":319},[272,1620,473],{"class":439},[272,1622,1598],{"class":319},[272,1624,478],{"class":282},[272,1626,405],{"class":319},[272,1628,211],{"class":439},[272,1630,405],{"class":319},[272,1632,487],{"class":439},[272,1634,579],{"class":319},[272,1636,1637,1640,1643],{"class":274,"line":374},[272,1638,1639],{"class":319},"sheet.add_table(",[272,1641,1642],{"class":282},"\"A1:C6\"",[272,1644,1645],{"class":319},", {\n",[272,1647,1648,1651,1654,1656,1658,1661,1664,1667,1669,1671],{"class":274,"line":385},[272,1649,1650],{"class":282},"    \"name\"",[272,1652,1653],{"class":319},": ",[272,1655,691],{"class":282},[272,1657,405],{"class":319},[272,1659,1660],{"class":282},"\"data\"",[272,1662,1663],{"class":319},": data, ",[272,1665,1666],{"class":282},"\"total_row\"",[272,1668,1653],{"class":319},[272,1670,793],{"class":439},[272,1672,728],{"class":319},[272,1674,1675,1678,1681,1684,1686,1688,1690,1693,1695,1698],{"class":274,"line":396},[272,1676,1677],{"class":282},"    \"columns\"",[272,1679,1680],{"class":319},": [{",[272,1682,1683],{"class":282},"\"header\"",[272,1685,1653],{"class":319},[272,1687,402],{"class":282},[272,1689,405],{"class":319},[272,1691,1692],{"class":282},"\"total_string\"",[272,1694,1653],{"class":319},[272,1696,1697],{"class":282},"\"Total\"",[272,1699,1700],{"class":319},"},\n",[272,1702,1703,1706,1708,1710,1712,1714,1717,1719,1722,1724,1727,1729,1731],{"class":274,"line":419},[272,1704,1705],{"class":319},"                {",[272,1707,1683],{"class":282},[272,1709,1653],{"class":319},[272,1711,408],{"class":282},[272,1713,405],{"class":319},[272,1715,1716],{"class":282},"\"total_function\"",[272,1718,1653],{"class":319},[272,1720,1721],{"class":282},"\"sum\"",[272,1723,405],{"class":319},[272,1725,1726],{"class":282},"\"total_value\"",[272,1728,1653],{"class":319},[272,1730,245],{"class":439},[272,1732,1700],{"class":319},[272,1734,1735,1737,1739,1741,1743,1745,1747,1749,1751,1753,1755,1757,1760],{"class":274,"line":493},[272,1736,1705],{"class":319},[272,1738,1683],{"class":282},[272,1740,1653],{"class":319},[272,1742,413],{"class":282},[272,1744,405],{"class":319},[272,1746,1716],{"class":282},[272,1748,1653],{"class":319},[272,1750,1721],{"class":282},[272,1752,405],{"class":319},[272,1754,1726],{"class":282},[272,1756,1653],{"class":319},[272,1758,1759],{"class":439},"4360.75",[272,1761,1762],{"class":319},"}],\n",[272,1764,1765],{"class":274,"line":499},[272,1766,1767],{"class":319},"})\n",[272,1769,1770],{"class":274,"line":514},[272,1771,1772],{"class":319},"book.close()\n",[272,1774,1775,1777,1780,1783],{"class":274,"line":1337},[272,1776,940],{"class":439},[272,1778,1779],{"class":319},"(",[272,1781,1782],{"class":282},"\"written\"",[272,1784,796],{"class":319},[10,1786,1787,1788,1791,1792,28],{},"The range again includes the totals row. ",[14,1789,1790],{},"total_value"," stores the result so readers that do not recalculate see the right figure, for the reasons covered in ",[19,1793,1795],{"href":1794},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fstore-formula-results-with-xlsxwriter\u002F","Store Formula Results with xlsxwriter So Every Viewer Shows Values",[30,1797,39,1802,39,1805,39,1808,39,1811,39,1817,39,1828,39,1833,39,1838,39,1843,39,1850,39,1854,39,1861,39,1865,39,1870,39,1874,39,1881,39,1884,39,1891,39,1896,39,1900,39,1904,39,1909,39,1912,39,1917,39,1920,39,1925,39,1928],{"viewBox":1798,"role":33,"ariaLabelledBy":1799,"xmlns":37,"style":964},"4 0 752 233",[1800,1801],"tr-libs-t","tr-libs-d",[41,1803,1804],{"id":1800},"openpyxl or XlsxWriter for a totals row",[45,1806,1807],{"id":1801},"openpyxl can add a totals row to a table in an existing workbook, but you must define the columns and write the SUBTOTAL formulas yourself. XlsxWriter writes the formulas automatically and can store their values, but only when creating a new file.",[49,1809],{"x":189,"y":52,"width":973,"height":1810,"fill":55},"233",[49,1812],{"x":977,"y":59,"width":1813,"height":1814,"rx":1815,"fill":121,"stroke":122,"style":1816},"351.0","201.0","14","stroke-width:2px",[1818,1819,1821],"g",{"transform":1820},"translate(182.5,29.0) scale(1.083)",[1822,1823],"path",{"d":1824,"fill":1825,"stroke":1826,"style":1827},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01","none","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[69,1829,1832],{"x":1830,"y":996,"style":1831},"195.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","openpyxl",[274,1834],{"x1":1835,"y1":1836,"x2":1837,"y2":1836,"stroke":122,"style":63},"36.0","91.0","355.0",[69,1839,1842],{"x":1840,"y":1014,"style":1841},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","works on existing workbooks",[1818,1844,1846],{"transform":1845},"translate(38.5,99.5) scale(0.625)",[1822,1847],{"d":1848,"fill":1825,"stroke":236,"style":1849},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[69,1851,1853],{"x":1840,"y":1852,"style":1841},"137.0","write SUBTOTAL formulas yourself",[1818,1855,1857],{"transform":1856},"translate(38.5,125.5) scale(0.625)",[1822,1858],{"d":1859,"fill":1825,"stroke":1860,"style":1849},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[69,1862,1864],{"x":1840,"y":1863,"style":1841},"163.0","no cached values stored",[1818,1866,1868],{"transform":1867},"translate(38.5,151.5) scale(0.625)",[1822,1869],{"d":1859,"fill":1825,"stroke":1860,"style":1849},[69,1871,1873],{"x":1840,"y":1872,"style":1841},"189.0","define TableColumns by hand",[1818,1875,1877],{"transform":1876},"translate(38.5,177.5) scale(0.625)",[1822,1878],{"d":1879,"fill":1825,"stroke":1880,"style":1849},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold,#b4740a)",[49,1882],{"x":1883,"y":59,"width":1813,"height":1814,"rx":1815,"fill":235,"stroke":236,"style":1816},"389.0",[1818,1885,1887],{"transform":1886},"translate(551.5,29.0) scale(1.083)",[1822,1888],{"d":1889,"fill":1825,"stroke":1890,"style":1827},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--teal-ink,#0b6157)",[69,1892,1895],{"x":1893,"y":996,"style":1894},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","XlsxWriter",[274,1897],{"x1":1898,"y1":1836,"x2":1899,"y2":1836,"stroke":236,"style":63},"405.0","724.0",[69,1901,1903],{"x":1902,"y":1014,"style":1841},"429.0","formulas written for you",[1818,1905,1907],{"transform":1906},"translate(407.5,99.5) scale(0.625)",[1822,1908],{"d":1848,"fill":1825,"stroke":236,"style":1849},[69,1910,1911],{"x":1902,"y":1852,"style":1841},"total_value caches results",[1818,1913,1915],{"transform":1914},"translate(407.5,125.5) scale(0.625)",[1822,1916],{"d":1848,"fill":1825,"stroke":236,"style":1849},[69,1918,1919],{"x":1902,"y":1863,"style":1841},"new files only",[1818,1921,1923],{"transform":1922},"translate(407.5,151.5) scale(0.625)",[1822,1924],{"d":1859,"fill":1825,"stroke":1860,"style":1849},[69,1926,1927],{"x":1902,"y":1872,"style":1841},"simplest for generated reports",[1818,1929,1931],{"transform":1930},"translate(407.5,177.5) scale(0.625)",[1822,1932],{"d":1848,"fill":1825,"stroke":236,"style":1849},[258,1934,1936],{"id":1935},"step-6-keep-the-totals-row-out-of-pandas","Step 6 — Keep the totals row out of pandas",[10,1938,1939,1942],{},[14,1940,1941],{},"pd.read_excel"," has no idea the last row is a total. Read only the data rows by counting the table's rows:",[263,1944,1946],{"className":306,"code":1945,"language":308,"meta":268,"style":268},"import pandas as pd\nfrom openpyxl import load_workbook\n\ntbl = load_workbook(\"sales_table.xlsx\")[\"Sales\"].tables[\"Sales\"]\nfirst, last = tbl.ref.split(\":\")\ndata_rows = int(last[1:]) - int(first[1:]) - (tbl.totalsRowCount or 0)\ndf = pd.read_excel(\"sales_table.xlsx\", sheet_name=\"Sales\", nrows=data_rows)\nprint(len(df), df[\"Amount\"].sum())               # 4 4360.75\n",[14,1947,1948,1961,1972,1976,2001,2016,2059,2090],{"__ignoreMap":268},[272,1949,1950,1952,1955,1958],{"class":274,"line":275},[272,1951,323],{"class":315},[272,1953,1954],{"class":319}," pandas ",[272,1956,1957],{"class":315},"as",[272,1959,1960],{"class":319}," pd\n",[272,1962,1963,1965,1967,1969],{"class":274,"line":329},[272,1964,316],{"class":315},[272,1966,320],{"class":319},[272,1968,323],{"class":315},[272,1970,1971],{"class":319}," load_workbook\n",[272,1973,1974],{"class":274,"line":342},[272,1975,359],{"emptyLinePlaceholder":358},[272,1977,1978,1981,1983,1986,1988,1991,1993,1996,1998],{"class":274,"line":355},[272,1979,1980],{"class":319},"tbl ",[272,1982,368],{"class":315},[272,1984,1985],{"class":319}," load_workbook(",[272,1987,933],{"class":282},[272,1989,1990],{"class":319},")[",[272,1992,691],{"class":282},[272,1994,1995],{"class":319},"].tables[",[272,1997,691],{"class":282},[272,1999,2000],{"class":319},"]\n",[272,2002,2003,2006,2008,2011,2014],{"class":274,"line":362},[272,2004,2005],{"class":319},"first, last ",[272,2007,368],{"class":315},[272,2009,2010],{"class":319}," tbl.ref.split(",[272,2012,2013],{"class":282},"\":\"",[272,2015,796],{"class":319},[272,2017,2018,2021,2023,2026,2029,2031,2034,2037,2039,2042,2044,2046,2048,2051,2054,2057],{"class":274,"line":374},[272,2019,2020],{"class":319},"data_rows ",[272,2022,368],{"class":315},[272,2024,2025],{"class":439}," int",[272,2027,2028],{"class":319},"(last[",[272,2030,118],{"class":439},[272,2032,2033],{"class":319},":]) ",[272,2035,2036],{"class":315},"-",[272,2038,2025],{"class":439},[272,2040,2041],{"class":319},"(first[",[272,2043,118],{"class":439},[272,2045,2033],{"class":319},[272,2047,2036],{"class":315},[272,2049,2050],{"class":319}," (tbl.totalsRowCount ",[272,2052,2053],{"class":315},"or",[272,2055,2056],{"class":439}," 0",[272,2058,796],{"class":319},[272,2060,2061,2064,2066,2069,2071,2073,2076,2078,2080,2082,2085,2087],{"class":274,"line":385},[272,2062,2063],{"class":319},"df ",[272,2065,368],{"class":315},[272,2067,2068],{"class":319}," pd.read_excel(",[272,2070,933],{"class":282},[272,2072,405],{"class":319},[272,2074,2075],{"class":592},"sheet_name",[272,2077,368],{"class":315},[272,2079,691],{"class":282},[272,2081,405],{"class":319},[272,2083,2084],{"class":592},"nrows",[272,2086,368],{"class":315},[272,2088,2089],{"class":319},"data_rows)\n",[272,2091,2092,2094,2096,2099,2102,2104,2107],{"class":274,"line":396},[272,2093,940],{"class":439},[272,2095,1779],{"class":319},[272,2097,2098],{"class":439},"len",[272,2100,2101],{"class":319},"(df), df[",[272,2103,413],{"class":282},[272,2105,2106],{"class":319},"].sum())               ",[272,2108,2109],{"class":510},"# 4 4360.75\n",[10,2111,2112,2113,28],{},"Without the adjustment the frame has a fifth row whose Amount doubles the sum. A reusable reader that handles headers, totals and table names is in ",[19,2114,2116],{"href":2115},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fread-an-excel-table-by-name-into-pandas\u002F","Read an Excel Table by Name into pandas",[258,2118,2120],{"id":2119},"step-7-add-rows-to-a-table-that-has-totals","Step 7 — Add rows to a table that has totals",[10,2122,2123,2124,2127],{},"Appending a row with ",[14,2125,2126],{},"ws.append"," puts it below the totals row, outside the table. To add data to an existing table, write the new rows where the totals row is, move the totals row down, and extend both ranges:",[263,2129,2131],{"className":306,"code":2130,"language":308,"meta":268,"style":268},"wb = load_workbook(\"sales_table.xlsx\")\nws = wb[\"Sales\"]\ntbl = ws.tables[\"Sales\"]\nold_total = int(tbl.ref.split(\":\")[1][1:])                         # 6\nnew_rows = [(\"Central\", 9, 870.0), (\"Islands\", 3, 215.5)]\nfor offset, row in enumerate(new_rows):\n    for col, value in enumerate(row, start=1):\n        ws.cell(old_total + offset, col, value)\nnew_total = old_total + len(new_rows)\nws[f\"A{new_total}\"] = \"Total\"\nws[f\"B{new_total}\"] = \"=SUBTOTAL(109,Sales[Orders])\"\nws[f\"C{new_total}\"] = \"=SUBTOTAL(109,Sales[Amount])\"\ntbl.ref = f\"A1:C{new_total}\"\ntbl.autoFilter.ref = f\"A1:C{new_total - 1}\"\nwb.save(\"sales_table.xlsx\")\nprint(tbl.ref, tbl.autoFilter.ref)             # A1:C8 A1:C7\n",[14,2132,2133,2145,2158,2171,2200,2238,2252,2275,2285,2303,2326,2348,2370,2391,2414,2422],{"__ignoreMap":268},[272,2134,2135,2137,2139,2141,2143],{"class":274,"line":275},[272,2136,365],{"class":319},[272,2138,368],{"class":315},[272,2140,1985],{"class":319},[272,2142,933],{"class":282},[272,2144,796],{"class":319},[272,2146,2147,2149,2151,2154,2156],{"class":274,"line":329},[272,2148,377],{"class":319},[272,2150,368],{"class":315},[272,2152,2153],{"class":319}," wb[",[272,2155,691],{"class":282},[272,2157,2000],{"class":319},[272,2159,2160,2162,2164,2167,2169],{"class":274,"line":342},[272,2161,1980],{"class":319},[272,2163,368],{"class":315},[272,2165,2166],{"class":319}," ws.tables[",[272,2168,691],{"class":282},[272,2170,2000],{"class":319},[272,2172,2173,2176,2178,2180,2183,2185,2187,2189,2192,2194,2197],{"class":274,"line":355},[272,2174,2175],{"class":319},"old_total ",[272,2177,368],{"class":315},[272,2179,2025],{"class":439},[272,2181,2182],{"class":319},"(tbl.ref.split(",[272,2184,2013],{"class":282},[272,2186,1990],{"class":319},[272,2188,118],{"class":439},[272,2190,2191],{"class":319},"][",[272,2193,118],{"class":439},[272,2195,2196],{"class":319},":])                         ",[272,2198,2199],{"class":510},"# 6\n",[272,2201,2202,2205,2207,2209,2212,2214,2217,2219,2222,2224,2227,2229,2231,2233,2236],{"class":274,"line":362},[272,2203,2204],{"class":319},"new_rows ",[272,2206,368],{"class":315},[272,2208,431],{"class":319},[272,2210,2211],{"class":282},"\"Central\"",[272,2213,405],{"class":319},[272,2215,2216],{"class":439},"9",[272,2218,405],{"class":319},[272,2220,2221],{"class":439},"870.0",[272,2223,447],{"class":319},[272,2225,2226],{"class":282},"\"Islands\"",[272,2228,405],{"class":319},[272,2230,167],{"class":439},[272,2232,405],{"class":319},[272,2234,2235],{"class":439},"215.5",[272,2237,628],{"class":319},[272,2239,2240,2242,2245,2247,2249],{"class":274,"line":374},[272,2241,422],{"class":315},[272,2243,2244],{"class":319}," offset, row ",[272,2246,428],{"class":315},[272,2248,615],{"class":439},[272,2250,2251],{"class":319},"(new_rows):\n",[272,2253,2254,2257,2260,2262,2264,2267,2269,2271,2273],{"class":274,"line":385},[272,2255,2256],{"class":315},"    for",[272,2258,2259],{"class":319}," col, value ",[272,2261,428],{"class":315},[272,2263,615],{"class":439},[272,2265,2266],{"class":319},"(row, ",[272,2268,621],{"class":592},[272,2270,368],{"class":315},[272,2272,118],{"class":439},[272,2274,1215],{"class":319},[272,2276,2277,2280,2282],{"class":274,"line":396},[272,2278,2279],{"class":319},"        ws.cell(old_total ",[272,2281,525],{"class":315},[272,2283,2284],{"class":319}," offset, col, value)\n",[272,2286,2287,2290,2292,2295,2297,2300],{"class":274,"line":419},[272,2288,2289],{"class":319},"new_total ",[272,2291,368],{"class":315},[272,2293,2294],{"class":319}," old_total ",[272,2296,525],{"class":315},[272,2298,2299],{"class":439}," len",[272,2301,2302],{"class":319},"(new_rows)\n",[272,2304,2305,2307,2309,2311,2313,2316,2318,2320,2322,2324],{"class":274,"line":493},[272,2306,852],{"class":319},[272,2308,700],{"class":315},[272,2310,857],{"class":282},[272,2312,707],{"class":706},[272,2314,2315],{"class":319},"new_total",[272,2317,713],{"class":706},[272,2319,716],{"class":282},[272,2321,868],{"class":319},[272,2323,368],{"class":315},[272,2325,643],{"class":282},[272,2327,2328,2330,2332,2334,2336,2338,2340,2342,2344,2346],{"class":274,"line":499},[272,2329,852],{"class":319},[272,2331,700],{"class":315},[272,2333,881],{"class":282},[272,2335,707],{"class":706},[272,2337,2315],{"class":319},[272,2339,713],{"class":706},[272,2341,716],{"class":282},[272,2343,868],{"class":319},[272,2345,368],{"class":315},[272,2347,896],{"class":282},[272,2349,2350,2352,2354,2356,2358,2360,2362,2364,2366,2368],{"class":274,"line":514},[272,2351,852],{"class":319},[272,2353,700],{"class":315},[272,2355,905],{"class":282},[272,2357,707],{"class":706},[272,2359,2315],{"class":319},[272,2361,713],{"class":706},[272,2363,716],{"class":282},[272,2365,868],{"class":319},[272,2367,368],{"class":315},[272,2369,920],{"class":282},[272,2371,2372,2375,2377,2380,2382,2384,2386,2388],{"class":274,"line":1337},[272,2373,2374],{"class":319},"tbl.ref ",[272,2376,368],{"class":315},[272,2378,2379],{"class":315}," f",[272,2381,703],{"class":282},[272,2383,707],{"class":706},[272,2385,2315],{"class":319},[272,2387,713],{"class":706},[272,2389,2390],{"class":282},"\"\n",[272,2392,2393,2396,2398,2400,2402,2404,2406,2408,2410,2412],{"class":274,"line":1348},[272,2394,2395],{"class":319},"tbl.autoFilter.ref ",[272,2397,368],{"class":315},[272,2399,2379],{"class":315},[272,2401,703],{"class":282},[272,2403,707],{"class":706},[272,2405,2289],{"class":319},[272,2407,2036],{"class":315},[272,2409,528],{"class":439},[272,2411,713],{"class":706},[272,2413,2390],{"class":282},[272,2415,2416,2418,2420],{"class":274,"line":1362},[272,2417,930],{"class":319},[272,2419,933],{"class":282},[272,2421,796],{"class":319},[272,2423,2424,2426,2429],{"class":274,"line":1385},[272,2425,940],{"class":439},[272,2427,2428],{"class":319},"(tbl.ref, tbl.autoFilter.ref)             ",[272,2430,2431],{"class":510},"# A1:C8 A1:C7\n",[10,2433,2434,2435,2437,2438,2441,2442,2444],{},"The structured references in the totals formulas need no change: ",[14,2436,952],{}," now covers the longer column automatically. What openpyxl will not do is update the ranges for you — ",[14,2439,2440],{},"insert_rows"," shifts cells but leaves the table's ",[14,2443,302],{}," where it was — so always set both ranges after changing the data.",[258,2446,2448],{"id":2447},"common-pitfalls","Common pitfalls",[2450,2451,2452,2460,2466,2475],"ul",{},[2453,2454,2455,2459],"li",{},[2456,2457,2458],"strong",{},"ref without the extra row."," If the range ends at the last data row, the \"totals row\" overwrites your data's last line in Excel's view.",[2453,2461,2462,2465],{},[2456,2463,2464],{},"Column names that differ from the header."," \"Amount \" with a space in the cell and \"Amount\" in the column definition make Excel repair the table.",[2453,2467,2468,2471,2472,2474],{},[2456,2469,2470],{},"Two tables with the same name."," ",[14,2473,686],{}," must be unique in the workbook; when writing one table per sheet, derive the name from the sheet.",[2453,2476,2477,2480,2481,2483],{},[2456,2478,2479],{},"SUM instead of SUBTOTAL."," A plain ",[14,2482,16],{}," ignores filters and double-counts if another total sits inside the range.",[258,2485,2487],{"id":2486},"conclusion","Conclusion",[10,2489,2490,2491,2493,2494,2497,2498,2500,2501,2503,2504,2507,2508,2511,2512,2514,2515,2518],{},"A table totals row needs three things that agree: a ",[14,2492,302],{}," one row longer than the data with ",[14,2495,2496],{},"totalsRowCount=1"," and an autofilter that stops above it, ",[14,2499,544],{}," definitions named exactly like the headers with ",[14,2502,801],{}," or ",[14,2505,2506],{},"totalsRowLabel",", and ",[14,2509,2510],{},"SUBTOTAL(109, Table[Column])"," formulas written into the cells — or ",[14,2513,830],{}," with a matching ",[14,2516,2517],{},"totalsRowFormula",". XlsxWriter does the formulas for you in new files. When reading the table back, drop the totals row before summing.",[258,2520,2522],{"id":2521},"frequently-asked-questions","Frequently asked questions",[10,2524,2525,2528],{},[2456,2526,2527],{},"How do I add a total row to an Excel table with openpyxl?","\nCreate the Table with a ref that includes one extra row and totalsRowCount=1, define each TableColumn with totalsRowFunction (such as \"sum\") or totalsRowLabel, and write the matching SUBTOTAL formulas into the totals row cells yourself.",[10,2530,2531,2534],{},[2456,2532,2533],{},"Why use SUBTOTAL(109, …) instead of SUM?","\nSUBTOTAL with function number 109 ignores rows hidden by a filter, so the total follows what the reader has filtered. It is also what Excel itself writes into a table's totals row.",[10,2536,2537,2540],{},[2456,2538,2539],{},"Do I have to write the formulas if I set totalsRowFunction?","\nWith openpyxl, yes. totalsRowFunction tells Excel which function the totals row uses, but openpyxl does not write the cell formula, so the cell would stay empty until someone re-selects the function in Excel.",[10,2542,2543,2546],{},[2456,2544,2545],{},"How do I stop pandas from reading the total row as data?","\nRead the table's range and drop the last totalsRowCount rows, or use nrows when reading with pandas. A helper that reads a table by name and drops its totals row keeps this in one place.",[258,2548,2550],{"id":2549},"related","Related",[2450,2552,2553,2559,2566,2573],{},[2453,2554,2555,2556,2558],{},"Up one level: ",[19,2557,22],{"href":21}," — tables, filters and grouping.",[2453,2560,2561,2565],{},[19,2562,2564],{"href":2563},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fapply-autofilter-criteria-and-hide-rows\u002F","Apply Autofilter Criteria and Hide the Filtered Rows with openpyxl"," — filters that the SUBTOTAL totals respond to.",[2453,2567,2568,2572],{},[19,2569,2571],{"href":2570},"\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"," — totals computed in pandas.",[2453,2574,2575,2579],{},[19,2576,2578],{"href":2577},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fadd-sum-and-average-formulas-with-python\u002F","Add SUM and AVERAGE Formulas with Python"," — plain formulas outside tables.",[2581,2582,2583],"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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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":268,"searchDepth":329,"depth":329,"links":2585},[2586,2587,2588,2589,2590,2591,2592,2593,2594,2595,2596,2597],{"id":260,"depth":329,"text":261},{"id":295,"depth":329,"text":296},{"id":537,"depth":329,"text":538},{"id":834,"depth":329,"text":835},{"id":1065,"depth":329,"text":1066},{"id":1518,"depth":329,"text":1519},{"id":1935,"depth":329,"text":1936},{"id":2119,"depth":329,"text":2120},{"id":2447,"depth":329,"text":2448},{"id":2486,"depth":329,"text":2487},{"id":2521,"depth":329,"text":2522},{"id":2549,"depth":329,"text":2550},"2026-09-23","Give an Excel table a real totals row from Python — the one readers can switch between Sum, Average and Count in Excel — with openpyxl's totalsRowCount and table column settings, SUBTOTAL formulas that respect filters, custom totals, the matching XlsxWriter options and how to keep the totals row out of pandas.","md",[2602,2604,2606,2608],{"q":2527,"a":2603},"Create the Table with a ref that includes one extra row and totalsRowCount=1, define each TableColumn with totalsRowFunction (such as \"sum\") or totalsRowLabel, and write the matching SUBTOTAL formulas into the totals row cells yourself.",{"q":2533,"a":2605},"SUBTOTAL with function number 109 ignores rows hidden by a filter, so the total follows what the reader has filtered. It is also what Excel itself writes into a table's totals row.",{"q":2539,"a":2607},"With openpyxl, yes. totalsRowFunction tells Excel which function the totals row uses, but openpyxl does not write the cell formula, so the cell would stay empty until someone re-selects the function in Excel.",{"q":2545,"a":2609},"Read the table's range and drop the last totalsRowCount rows, or use nrows when reading with pandas. A helper that reads a table by name and drops its totals row keeps this in one place.","add a total row to an excel table with python",null,{"breadcrumb":2613},[2614,2617,2620],{"name":2615,"item":2616},"Home","\u002F",{"name":2618,"item":2619},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":22,"item":21},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table",{"title":2623,"description":2624},"Add a Total Row to an Excel Table with Python (openpyxl)","Excel table totals rows from Python — totalsRowCount, TableColumn totalsRowFunction and totalsRowLabel, SUBTOTAL(109) formulas with structured references, custom totals, the autoFilter range, XlsxWriter total_row and reading the table without its totals.","add-a-total-row-to-an-excel-table","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-a-total-row-to-an-excel-table\u002Findex",[2628,2629,1832],"tables-and-filters","excel-formulas","how-to","h66JMG1Vs_25z0euG5fH_RYRiQNC2OlszR05YSXzHc0",[2633,2636],{"title":22,"path":2634,"stem":2635,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Findex",{"title":2637,"path":2638,"stem":2639,"children":-1},"Add an Autofilter to an Excel Sheet with openpyxl","\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl","formatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-an-autofilter-to-an-excel-sheet-with-openpyxl\u002Findex",[2641,2642],{"path":2634,"title":22},{"path":2643,"title":2618},"\u002Fformatting-and-charting-excel-reports-with-python",[],[2646,2649,2652,2655,2658,2659],{"path":2647,"title":2648,"kind":2630},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fapply-conditional-formatting-to-an-excel-table-that-grows","Apply Conditional Formatting to an Excel Table That Grows",{"path":2650,"title":2651,"kind":2630},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fsort-and-filter-an-excel-online-table-with-microsoft-graph","Sort and Filter an Excel Online Table with Microsoft Graph",{"path":2653,"title":2654,"kind":2630},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-an-excel-table-with-formula-columns-using-xlsxwriter","Write an Excel Table with Formula Columns Using XlsxWriter",{"path":2656,"title":2657,"kind":2630},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-charts-in-excel-with-openpyxl\u002Fmake-an-excel-chart-grow-with-its-data","Make an Excel Chart Grow with Its Data",{"path":2638,"title":2637,"kind":2630},{"path":2660,"title":2661,"kind":2630},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fadd-calculated-columns-to-an-excel-table-with-python","Add Calculated Columns to an Excel Table with Python",1790972115708]