[{"data":1,"prerenderedAt":2795},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python":2786},{"id":4,"title":5,"body":6,"dateModified":2763,"datePublished":2763,"description":2764,"extension":2765,"faq":2766,"meta":2777,"navigation":251,"path":2778,"seo":2779,"slug":2782,"stem":2783,"type":2784,"__hash__":2785},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python\u002Findex.md","Add a Native Excel Pivot Table with Python",{"type":7,"value":8,"toc":2751},"minimark",[9,28,171,176,221,224,485,489,497,826,837,841,938,953,1267,1273,1281,1285,1288,1657,1668,1685,1754,1758,1761,1866,2395,2402,2406,2528,2532,2535,2541,2552,2558,2651,2658,2661,2665,2671,2675,2681,2687,2693,2702,2708,2712,2747],[10,11,12,16,17,21,22,27],"p",{},[13,14,15],"code",{},"pandas.pivot_table"," produces a grid of numbers. A ",[18,19,20],"em",{},"native"," Excel pivot table is something different: a live object the reader can pivot, filter, drill into and refresh. Producing one from Python is harder than it should be, because the pivot definition and its cache are among the few Excel structures openpyxl cannot build from scratch. This guide covers the three routes that actually work — a template with the pivot already in it, driving Excel through xlwings, and the portable static fallback — and how to choose between them. It extends ",[23,24,26],"a",{"href":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data",".",[29,30,39,40,39,44,39,48,39,55,39,62,39,71,39,77,39,82,39,86,39,92,39,97,39,100,39,104,39,109,39,114,39,118,39,121,39,124,39,129,39,133,39,135,39,138,39,141,39,146,39,151,39,154,39,157,39,160,39,163,39,165,39,168],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"0 0 800 264","img","Three routes to a pivot table: a template carrying the pivot with refresh on load, xlwings driving Excel on Windows, and a static pandas grid that works everywhere.",[35,36],"pv-t","pv-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;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},"Three routes to putting a pivot in front of a reader",[45,46,47],"desc",{"id":36},"Three approaches compared on portability and interactivity. A template workbook that already contains the pivot works anywhere, because the script only writes source rows and flags the cache to refresh on open — the reader gets a live pivot. Driving Excel with xlwings builds the pivot programmatically and is fully flexible, but requires Windows with Excel installed. A pandas pivot table written as a formatted grid runs anywhere including a Linux container, but the result is static values the reader cannot re-pivot.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","800","264","#ffffff",[56,57,61],"text",{"x":58,"y":59,"style":60},"400","26","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","portability and interactivity pull in opposite directions",[49,63],{"x":64,"y":65,"width":66,"height":67,"rx":64,"fill":68,"stroke":69,"style":70},"14","42","248","204","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[56,72,76],{"x":73,"y":74,"style":75},"138","70","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","template + refresh",[56,78,81],{"x":73,"y":79,"style":80},"100","font-size:11px;fill:var(--text,#172033);text-anchor:middle","the pivot already exists",[56,83,85],{"x":73,"y":84,"style":80},"124","the script writes rows only",[49,87],{"x":88,"y":89,"width":90,"height":59,"rx":91,"fill":54,"stroke":69},"40","140","196","6",[56,93,96],{"x":73,"y":94,"style":95},"158","font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","runs anywhere",[49,98],{"x":88,"y":99,"width":90,"height":59,"rx":91,"fill":54,"stroke":69},"172",[56,101,103],{"x":73,"y":102,"style":95},"190","stays interactive",[56,105,108],{"x":73,"y":106,"style":107},"224","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","needs the template built once",[49,110],{"x":111,"y":65,"width":66,"height":67,"rx":64,"fill":112,"stroke":113,"style":70},"276","#ebebfd","var(--brand,#5b5cf0)",[56,115,117],{"x":58,"y":74,"style":116},"font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","xlwings drives Excel",[56,119,120],{"x":58,"y":79,"style":80},"builds the pivot in code",[56,122,123],{"x":58,"y":84,"style":80},"full control of layout",[49,125],{"x":126,"y":89,"width":90,"height":59,"rx":91,"fill":127,"stroke":128},"302","#fee8f2","var(--accent,#f43f8f)",[56,130,132],{"x":58,"y":94,"style":131},"font-size:10.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Windows + Excel only",[49,134],{"x":126,"y":99,"width":90,"height":59,"rx":91,"fill":54,"stroke":113},[56,136,103],{"x":58,"y":102,"style":137},"font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[56,139,140],{"x":58,"y":106,"style":107},"no good on a server",[49,142],{"x":143,"y":65,"width":66,"height":67,"rx":64,"fill":144,"stroke":145,"style":70},"538","#fdefd8","var(--gold,#b4740a)",[56,147,150],{"x":148,"y":74,"style":149},"662","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","static pandas grid",[56,152,153],{"x":148,"y":79,"style":80},"pivot_table, then format",[56,155,156],{"x":148,"y":84,"style":80},"plus the raw data sheet",[49,158],{"x":159,"y":89,"width":90,"height":59,"rx":91,"fill":54,"stroke":145},"564",[56,161,96],{"x":148,"y":94,"style":162},"font-size:10.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[49,164],{"x":159,"y":99,"width":90,"height":59,"rx":91,"fill":127,"stroke":128},[56,166,167],{"x":148,"y":102,"style":131},"values only, not live",[56,169,170],{"x":148,"y":106,"style":107},"the honest default",[172,173,175],"h2",{"id":174},"prerequisites","Prerequisites",[177,178,183],"pre",{"className":179,"code":180,"language":181,"meta":182,"style":182},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl xlsxwriter\npip install xlwings          # only for the Windows route\n","bash","",[13,184,185,207],{"__ignoreMap":182},[186,187,190,194,198,201,204],"span",{"class":188,"line":189},"line",1,[186,191,193],{"class":192},"sMTad","pip",[186,195,197],{"class":196},"srMev"," install",[186,199,200],{"class":196}," pandas",[186,202,203],{"class":196}," openpyxl",[186,205,206],{"class":196}," xlsxwriter\n",[186,208,210,212,214,217],{"class":188,"line":209},2,[186,211,193],{"class":192},[186,213,197],{"class":196},[186,215,216],{"class":196}," xlwings",[186,218,220],{"class":219},"s-wDw","          # only for the Windows route\n",[10,222,223],{},"Some source data to pivot:",[177,225,229],{"className":226,"code":227,"language":228,"meta":182,"style":182},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\n    \"date\": pd.to_datetime(\n        [\"2026-06-03\", \"2026-06-19\", \"2026-07-02\", \"2026-07-22\",\n         \"2026-08-05\", \"2026-08-15\"] * 2\n    ),\n    \"region\": [\"North\", \"South\", \"West\", \"North\", \"South\", \"West\"] * 2,\n    \"product\": [\"Widget\"] * 6 + [\"Gadget\"] * 6,\n    \"revenue\": [5150.00, 4268.50, 3511.25, 2980.10, 3140.75, 1820.00,\n                4210.00, 3980.25, 2711.50, 3320.80, 2905.60, 1615.40],\n})\n","python",[13,230,231,247,253,265,274,302,323,329,372,407,445,479],{"__ignoreMap":182},[186,232,233,237,241,244],{"class":188,"line":189},[186,234,236],{"class":235},"s-kum","import",[186,238,240],{"class":239},"skGVy"," pandas ",[186,242,243],{"class":235},"as",[186,245,246],{"class":239}," pd\n",[186,248,249],{"class":188,"line":209},[186,250,252],{"emptyLinePlaceholder":251},true,"\n",[186,254,256,259,262],{"class":188,"line":255},3,[186,257,258],{"class":239},"sales ",[186,260,261],{"class":235},"=",[186,263,264],{"class":239}," pd.DataFrame({\n",[186,266,268,271],{"class":188,"line":267},4,[186,269,270],{"class":196},"    \"date\"",[186,272,273],{"class":239},": pd.to_datetime(\n",[186,275,277,280,283,286,289,291,294,296,299],{"class":188,"line":276},5,[186,278,279],{"class":239},"        [",[186,281,282],{"class":196},"\"2026-06-03\"",[186,284,285],{"class":239},", ",[186,287,288],{"class":196},"\"2026-06-19\"",[186,290,285],{"class":239},[186,292,293],{"class":196},"\"2026-07-02\"",[186,295,285],{"class":239},[186,297,298],{"class":196},"\"2026-07-22\"",[186,300,301],{"class":239},",\n",[186,303,305,308,310,313,316,319],{"class":188,"line":304},6,[186,306,307],{"class":196},"         \"2026-08-05\"",[186,309,285],{"class":239},[186,311,312],{"class":196},"\"2026-08-15\"",[186,314,315],{"class":239},"] ",[186,317,318],{"class":235},"*",[186,320,322],{"class":321},"sP0c6"," 2\n",[186,324,326],{"class":188,"line":325},7,[186,327,328],{"class":239},"    ),\n",[186,330,332,335,338,341,343,346,348,351,353,355,357,359,361,363,365,367,370],{"class":188,"line":331},8,[186,333,334],{"class":196},"    \"region\"",[186,336,337],{"class":239},": [",[186,339,340],{"class":196},"\"North\"",[186,342,285],{"class":239},[186,344,345],{"class":196},"\"South\"",[186,347,285],{"class":239},[186,349,350],{"class":196},"\"West\"",[186,352,285],{"class":239},[186,354,340],{"class":196},[186,356,285],{"class":239},[186,358,345],{"class":196},[186,360,285],{"class":239},[186,362,350],{"class":196},[186,364,315],{"class":239},[186,366,318],{"class":235},[186,368,369],{"class":321}," 2",[186,371,301],{"class":239},[186,373,375,378,380,383,385,387,390,393,396,399,401,403,405],{"class":188,"line":374},9,[186,376,377],{"class":196},"    \"product\"",[186,379,337],{"class":239},[186,381,382],{"class":196},"\"Widget\"",[186,384,315],{"class":239},[186,386,318],{"class":235},[186,388,389],{"class":321}," 6",[186,391,392],{"class":235}," +",[186,394,395],{"class":239}," [",[186,397,398],{"class":196},"\"Gadget\"",[186,400,315],{"class":239},[186,402,318],{"class":235},[186,404,389],{"class":321},[186,406,301],{"class":239},[186,408,410,413,415,418,420,423,425,428,430,433,435,438,440,443],{"class":188,"line":409},10,[186,411,412],{"class":196},"    \"revenue\"",[186,414,337],{"class":239},[186,416,417],{"class":321},"5150.00",[186,419,285],{"class":239},[186,421,422],{"class":321},"4268.50",[186,424,285],{"class":239},[186,426,427],{"class":321},"3511.25",[186,429,285],{"class":239},[186,431,432],{"class":321},"2980.10",[186,434,285],{"class":239},[186,436,437],{"class":321},"3140.75",[186,439,285],{"class":239},[186,441,442],{"class":321},"1820.00",[186,444,301],{"class":239},[186,446,448,451,453,456,458,461,463,466,468,471,473,476],{"class":188,"line":447},11,[186,449,450],{"class":321},"                4210.00",[186,452,285],{"class":239},[186,454,455],{"class":321},"3980.25",[186,457,285],{"class":239},[186,459,460],{"class":321},"2711.50",[186,462,285],{"class":239},[186,464,465],{"class":321},"3320.80",[186,467,285],{"class":239},[186,469,470],{"class":321},"2905.60",[186,472,285],{"class":239},[186,474,475],{"class":321},"1615.40",[186,477,478],{"class":239},"],\n",[186,480,482],{"class":188,"line":481},12,[186,483,484],{"class":239},"})\n",[172,486,488],{"id":487},"step-1-write-the-source-as-a-named-table","Step 1 — Write the source as a named table",[10,490,491,492,496],{},"Whichever route you take, the source should be a ",[493,494,495],"strong",{},"named Excel table"," rather than a plain range. A table's reference expands automatically as rows are added, so a pivot pointed at it never needs its source updating:",[177,498,500],{"className":226,"code":499,"language":228,"meta":182,"style":182},"import pandas as pd\n\ndef write_source_table(df, path, sheet_name=\"Data\", table_name=\"SalesData\"):\n    \"\"\"Write the source rows as a named Excel table, ready for a pivot.\"\"\"\n    with pd.ExcelWriter(path, engine=\"xlsxwriter\",\n                        datetime_format=\"yyyy-mm-dd\") as writer:\n        df.to_excel(writer, sheet_name=sheet_name, index=False, startrow=0)\n        sheet = writer.sheets[sheet_name]\n\n        sheet.add_table(\n            0, 0, len(df), len(df.columns) - 1,\n            {\n                \"name\": table_name,\n                \"columns\": [{\"header\": str(c)} for c in df.columns],\n                \"style\": \"Table Style Medium 2\",\n            },\n        )\n        sheet.set_column(\"A:A\", 13)\n        sheet.set_column(\"B:C\", 14)\n        sheet.set_column(\"D:D\", 14,\n                         writer.book.add_format({\"num_format\": \"#,##0.00\"}))\n    return path\n\nwrite_source_table(sales, \"sales.xlsx\")\n",[13,501,502,512,516,544,549,568,586,619,629,633,638,668,673,682,715,728,734,740,756,770,784,801,810,815],{"__ignoreMap":182},[186,503,504,506,508,510],{"class":188,"line":189},[186,505,236],{"class":235},[186,507,240],{"class":239},[186,509,243],{"class":235},[186,511,246],{"class":239},[186,513,514],{"class":188,"line":209},[186,515,252],{"emptyLinePlaceholder":251},[186,517,518,521,525,528,530,533,536,538,541],{"class":188,"line":255},[186,519,520],{"class":235},"def",[186,522,524],{"class":523},"s_Opv"," write_source_table",[186,526,527],{"class":239},"(df, path, sheet_name",[186,529,261],{"class":235},[186,531,532],{"class":196},"\"Data\"",[186,534,535],{"class":239},", table_name",[186,537,261],{"class":235},[186,539,540],{"class":196},"\"SalesData\"",[186,542,543],{"class":239},"):\n",[186,545,546],{"class":188,"line":267},[186,547,548],{"class":196},"    \"\"\"Write the source rows as a named Excel table, ready for a pivot.\"\"\"\n",[186,550,551,554,557,561,563,566],{"class":188,"line":276},[186,552,553],{"class":235},"    with",[186,555,556],{"class":239}," pd.ExcelWriter(path, ",[186,558,560],{"class":559},"sa561","engine",[186,562,261],{"class":235},[186,564,565],{"class":196},"\"xlsxwriter\"",[186,567,301],{"class":239},[186,569,570,573,575,578,581,583],{"class":188,"line":304},[186,571,572],{"class":559},"                        datetime_format",[186,574,261],{"class":235},[186,576,577],{"class":196},"\"yyyy-mm-dd\"",[186,579,580],{"class":239},") ",[186,582,243],{"class":235},[186,584,585],{"class":239}," writer:\n",[186,587,588,591,594,596,599,602,604,607,609,612,614,616],{"class":188,"line":325},[186,589,590],{"class":239},"        df.to_excel(writer, ",[186,592,593],{"class":559},"sheet_name",[186,595,261],{"class":235},[186,597,598],{"class":239},"sheet_name, ",[186,600,601],{"class":559},"index",[186,603,261],{"class":235},[186,605,606],{"class":321},"False",[186,608,285],{"class":239},[186,610,611],{"class":559},"startrow",[186,613,261],{"class":235},[186,615,51],{"class":321},[186,617,618],{"class":239},")\n",[186,620,621,624,626],{"class":188,"line":331},[186,622,623],{"class":239},"        sheet ",[186,625,261],{"class":235},[186,627,628],{"class":239}," writer.sheets[sheet_name]\n",[186,630,631],{"class":188,"line":374},[186,632,252],{"emptyLinePlaceholder":251},[186,634,635],{"class":188,"line":409},[186,636,637],{"class":239},"        sheet.add_table(\n",[186,639,640,643,645,647,649,652,655,657,660,663,666],{"class":188,"line":447},[186,641,642],{"class":321},"            0",[186,644,285],{"class":239},[186,646,51],{"class":321},[186,648,285],{"class":239},[186,650,651],{"class":321},"len",[186,653,654],{"class":239},"(df), ",[186,656,651],{"class":321},[186,658,659],{"class":239},"(df.columns) ",[186,661,662],{"class":235},"-",[186,664,665],{"class":321}," 1",[186,667,301],{"class":239},[186,669,670],{"class":188,"line":481},[186,671,672],{"class":239},"            {\n",[186,674,676,679],{"class":188,"line":675},13,[186,677,678],{"class":196},"                \"name\"",[186,680,681],{"class":239},": table_name,\n",[186,683,685,688,691,694,697,700,703,706,709,712],{"class":188,"line":684},14,[186,686,687],{"class":196},"                \"columns\"",[186,689,690],{"class":239},": [{",[186,692,693],{"class":196},"\"header\"",[186,695,696],{"class":239},": ",[186,698,699],{"class":321},"str",[186,701,702],{"class":239},"(c)} ",[186,704,705],{"class":235},"for",[186,707,708],{"class":239}," c ",[186,710,711],{"class":235},"in",[186,713,714],{"class":239}," df.columns],\n",[186,716,718,721,723,726],{"class":188,"line":717},15,[186,719,720],{"class":196},"                \"style\"",[186,722,696],{"class":239},[186,724,725],{"class":196},"\"Table Style Medium 2\"",[186,727,301],{"class":239},[186,729,731],{"class":188,"line":730},16,[186,732,733],{"class":239},"            },\n",[186,735,737],{"class":188,"line":736},17,[186,738,739],{"class":239},"        )\n",[186,741,743,746,749,751,754],{"class":188,"line":742},18,[186,744,745],{"class":239},"        sheet.set_column(",[186,747,748],{"class":196},"\"A:A\"",[186,750,285],{"class":239},[186,752,753],{"class":321},"13",[186,755,618],{"class":239},[186,757,759,761,764,766,768],{"class":188,"line":758},19,[186,760,745],{"class":239},[186,762,763],{"class":196},"\"B:C\"",[186,765,285],{"class":239},[186,767,64],{"class":321},[186,769,618],{"class":239},[186,771,773,775,778,780,782],{"class":188,"line":772},20,[186,774,745],{"class":239},[186,776,777],{"class":196},"\"D:D\"",[186,779,285],{"class":239},[186,781,64],{"class":321},[186,783,301],{"class":239},[186,785,787,790,793,795,798],{"class":188,"line":786},21,[186,788,789],{"class":239},"                         writer.book.add_format({",[186,791,792],{"class":196},"\"num_format\"",[186,794,696],{"class":239},[186,796,797],{"class":196},"\"#,##0.00\"",[186,799,800],{"class":239},"}))\n",[186,802,804,807],{"class":188,"line":803},22,[186,805,806],{"class":235},"    return",[186,808,809],{"class":239}," path\n",[186,811,813],{"class":188,"line":812},23,[186,814,252],{"emptyLinePlaceholder":251},[186,816,818,821,824],{"class":188,"line":817},24,[186,819,820],{"class":239},"write_source_table(sales, ",[186,822,823],{"class":196},"\"sales.xlsx\"",[186,825,618],{"class":239},[10,827,828,829,832,833,27],{},"Two things this buys you beyond the pivot. Readers get filter dropdowns for free, and any formula elsewhere can reference ",[13,830,831],{},"SalesData[revenue]"," rather than a range that goes stale. The table mechanics are covered in ",[23,834,836],{"href":835},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","creating an Excel table with Python",[172,838,840],{"id":839},"step-2-the-template-route","Step 2 — The template route",[29,842,39,848,39,851,39,854,39,857,39,864,39,870,39,875,39,880,39,883,39,887,39,891,39,896,39,901,39,907,39,913,39,918,39,921,39,924,39,928,39,930,39,934],{"viewBox":843,"role":32,"ariaLabel":844,"ariaLabelledBy":845,"xmlns":37,"style":38},"0 0 800 230","The template route in three moves: replace the data sheet's rows, grow the named table reference to match, and set the pivot cache to refresh when Excel opens the file.",[846,847],"tmpl-t","tmpl-d",[41,849,850],{"id":846},"What the script touches, and what the template keeps",[45,852,853],{"id":847},"A template workbook holds a Data sheet with a named table and a Pivot sheet with a pivot pointing at it. Each run the script does exactly three things: it clears and rewrites the data rows, it extends the table reference so the new last row is included, and it sets the pivot cache's refreshOnLoad flag. The pivot definition, its layout and its formatting are never touched, and Excel rebuilds the cache the moment a reader opens the file.",[49,855],{"x":51,"y":51,"width":52,"height":856,"fill":54},"230",[49,858],{"x":64,"y":859,"width":860,"height":861,"rx":64,"fill":862,"stroke":863,"style":70},"34","300","176","#f0f2f5","var(--line,#cdd5e6)",[56,865,869],{"x":866,"y":867,"style":868},"164","60","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","the template",[49,871],{"x":65,"y":872,"width":873,"height":859,"rx":874,"fill":112,"stroke":113,"style":70},"76","244","7",[56,876,879],{"x":866,"y":877,"style":878},"98","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Data · named table SalesData",[49,881],{"x":65,"y":882,"width":873,"height":859,"rx":874,"fill":68,"stroke":69,"style":70},"118",[56,884,886],{"x":866,"y":89,"style":885},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Pivot · built once, by hand",[56,888,890],{"x":866,"y":889,"style":107},"180","layout and formatting never touched",[188,892],{"x1":893,"y1":894,"x2":895,"y2":894,"stroke":113,"style":70},"314","122","348",[897,898],"polygon",{"points":899,"fill":900},"356,122 344,116 344,128","#5b5cf0",[49,902],{"x":903,"y":859,"width":904,"height":905,"rx":906,"fill":144,"stroke":145,"style":70},"364","420","52","11",[56,908,912],{"x":909,"y":910,"style":911},"388","56","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06)","1 · clear and rewrite the data rows",[56,914,917],{"x":909,"y":915,"style":916},"75","font-size:10.5px;fill:var(--muted,#5b6780)","delete_rows then append — the header stays",[49,919],{"x":903,"y":920,"width":904,"height":905,"rx":906,"fill":144,"stroke":145,"style":70},"96",[56,922,923],{"x":909,"y":882,"style":911},"2 · grow the table reference",[56,925,927],{"x":909,"y":926,"style":916},"137","so the pivot's source covers every new row",[49,929],{"x":903,"y":94,"width":904,"height":905,"rx":906,"fill":68,"stroke":69,"style":70},[56,931,933],{"x":909,"y":889,"style":932},"font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157)","3 · cache.refreshOnLoad = True",[56,935,937],{"x":909,"y":936,"style":916},"199","Excel rebuilds the cache when the reader opens it",[10,939,940,941,944,945,948,949,952],{},"This is the approach that works on a server ",[18,942,943],{},"and"," gives readers a live pivot. Build the pivot once, by hand, in a template workbook: a ",[13,946,947],{},"Data"," sheet with the named table, and a ",[13,950,951],{},"Pivot"," sheet with a pivot table pointed at it. Then the script only ever writes rows and flags the cache.",[177,954,956],{"className":226,"code":955,"language":228,"meta":182,"style":182},"from openpyxl import load_workbook\nimport pandas as pd\n\ndef refresh_template(template, dest, df, sheet_name=\"Data\"):\n    \"\"\"Fill a template's data sheet and mark its pivot caches to refresh.\"\"\"\n    wb = load_workbook(template)\n    ws = wb[sheet_name]\n\n    # Clear previous rows, keeping the header.\n    if ws.max_row > 1:\n        ws.delete_rows(2, ws.max_row - 1)\n\n    for record in df.itertuples(index=False):\n        ws.append(list(record))\n\n    # Grow the table reference so the pivot sees every row.\n    for table in ws.tables.values():\n        first, _, last_col = table.ref.partition(\":\")\n        column_letters = \"\".join(c for c in last_col if c.isalpha())\n        table.ref = f\"{first}:{column_letters}{len(df) + 1}\"\n\n    # Ask Excel to rebuild the pivot cache when the file opens.\n    for pivot in wb._pivots:\n        pivot.cache.refreshOnLoad = True\n\n    wb.save(dest)\n    return dest\n",[13,957,958,971,981,985,1001,1006,1016,1026,1030,1035,1051,1068,1072,1093,1104,1108,1113,1125,1140,1168,1217,1221,1226,1238,1248,1253,1259],{"__ignoreMap":182},[186,959,960,963,966,968],{"class":188,"line":189},[186,961,962],{"class":235},"from",[186,964,965],{"class":239}," openpyxl ",[186,967,236],{"class":235},[186,969,970],{"class":239}," load_workbook\n",[186,972,973,975,977,979],{"class":188,"line":209},[186,974,236],{"class":235},[186,976,240],{"class":239},[186,978,243],{"class":235},[186,980,246],{"class":239},[186,982,983],{"class":188,"line":255},[186,984,252],{"emptyLinePlaceholder":251},[186,986,987,989,992,995,997,999],{"class":188,"line":267},[186,988,520],{"class":235},[186,990,991],{"class":523}," refresh_template",[186,993,994],{"class":239},"(template, dest, df, sheet_name",[186,996,261],{"class":235},[186,998,532],{"class":196},[186,1000,543],{"class":239},[186,1002,1003],{"class":188,"line":276},[186,1004,1005],{"class":196},"    \"\"\"Fill a template's data sheet and mark its pivot caches to refresh.\"\"\"\n",[186,1007,1008,1011,1013],{"class":188,"line":304},[186,1009,1010],{"class":239},"    wb ",[186,1012,261],{"class":235},[186,1014,1015],{"class":239}," load_workbook(template)\n",[186,1017,1018,1021,1023],{"class":188,"line":325},[186,1019,1020],{"class":239},"    ws ",[186,1022,261],{"class":235},[186,1024,1025],{"class":239}," wb[sheet_name]\n",[186,1027,1028],{"class":188,"line":331},[186,1029,252],{"emptyLinePlaceholder":251},[186,1031,1032],{"class":188,"line":374},[186,1033,1034],{"class":219},"    # Clear previous rows, keeping the header.\n",[186,1036,1037,1040,1043,1046,1048],{"class":188,"line":409},[186,1038,1039],{"class":235},"    if",[186,1041,1042],{"class":239}," ws.max_row ",[186,1044,1045],{"class":235},">",[186,1047,665],{"class":321},[186,1049,1050],{"class":239},":\n",[186,1052,1053,1056,1059,1062,1064,1066],{"class":188,"line":447},[186,1054,1055],{"class":239},"        ws.delete_rows(",[186,1057,1058],{"class":321},"2",[186,1060,1061],{"class":239},", ws.max_row ",[186,1063,662],{"class":235},[186,1065,665],{"class":321},[186,1067,618],{"class":239},[186,1069,1070],{"class":188,"line":481},[186,1071,252],{"emptyLinePlaceholder":251},[186,1073,1074,1077,1080,1082,1085,1087,1089,1091],{"class":188,"line":675},[186,1075,1076],{"class":235},"    for",[186,1078,1079],{"class":239}," record ",[186,1081,711],{"class":235},[186,1083,1084],{"class":239}," df.itertuples(",[186,1086,601],{"class":559},[186,1088,261],{"class":235},[186,1090,606],{"class":321},[186,1092,543],{"class":239},[186,1094,1095,1098,1101],{"class":188,"line":684},[186,1096,1097],{"class":239},"        ws.append(",[186,1099,1100],{"class":321},"list",[186,1102,1103],{"class":239},"(record))\n",[186,1105,1106],{"class":188,"line":717},[186,1107,252],{"emptyLinePlaceholder":251},[186,1109,1110],{"class":188,"line":730},[186,1111,1112],{"class":219},"    # Grow the table reference so the pivot sees every row.\n",[186,1114,1115,1117,1120,1122],{"class":188,"line":736},[186,1116,1076],{"class":235},[186,1118,1119],{"class":239}," table ",[186,1121,711],{"class":235},[186,1123,1124],{"class":239}," ws.tables.values():\n",[186,1126,1127,1130,1132,1135,1138],{"class":188,"line":742},[186,1128,1129],{"class":239},"        first, _, last_col ",[186,1131,261],{"class":235},[186,1133,1134],{"class":239}," table.ref.partition(",[186,1136,1137],{"class":196},"\":\"",[186,1139,618],{"class":239},[186,1141,1142,1145,1147,1150,1153,1155,1157,1159,1162,1165],{"class":188,"line":758},[186,1143,1144],{"class":239},"        column_letters ",[186,1146,261],{"class":235},[186,1148,1149],{"class":196}," \"\"",[186,1151,1152],{"class":239},".join(c ",[186,1154,705],{"class":235},[186,1156,708],{"class":239},[186,1158,711],{"class":235},[186,1160,1161],{"class":239}," last_col ",[186,1163,1164],{"class":235},"if",[186,1166,1167],{"class":239}," c.isalpha())\n",[186,1169,1170,1173,1175,1178,1181,1185,1188,1191,1194,1196,1199,1202,1204,1207,1210,1212,1214],{"class":188,"line":772},[186,1171,1172],{"class":239},"        table.ref ",[186,1174,261],{"class":235},[186,1176,1177],{"class":235}," f",[186,1179,1180],{"class":196},"\"",[186,1182,1184],{"class":1183},"sSjpA","{",[186,1186,1187],{"class":239},"first",[186,1189,1190],{"class":1183},"}",[186,1192,1193],{"class":196},":",[186,1195,1184],{"class":1183},[186,1197,1198],{"class":239},"column_letters",[186,1200,1201],{"class":1183},"}{",[186,1203,651],{"class":321},[186,1205,1206],{"class":239},"(df) ",[186,1208,1209],{"class":235},"+",[186,1211,665],{"class":321},[186,1213,1190],{"class":1183},[186,1215,1216],{"class":196},"\"\n",[186,1218,1219],{"class":188,"line":786},[186,1220,252],{"emptyLinePlaceholder":251},[186,1222,1223],{"class":188,"line":803},[186,1224,1225],{"class":219},"    # Ask Excel to rebuild the pivot cache when the file opens.\n",[186,1227,1228,1230,1233,1235],{"class":188,"line":812},[186,1229,1076],{"class":235},[186,1231,1232],{"class":239}," pivot ",[186,1234,711],{"class":235},[186,1236,1237],{"class":239}," wb._pivots:\n",[186,1239,1240,1243,1245],{"class":188,"line":817},[186,1241,1242],{"class":239},"        pivot.cache.refreshOnLoad ",[186,1244,261],{"class":235},[186,1246,1247],{"class":321}," True\n",[186,1249,1251],{"class":188,"line":1250},25,[186,1252,252],{"emptyLinePlaceholder":251},[186,1254,1256],{"class":188,"line":1255},26,[186,1257,1258],{"class":239},"    wb.save(dest)\n",[186,1260,1262,1264],{"class":188,"line":1261},27,[186,1263,806],{"class":235},[186,1265,1266],{"class":239}," dest\n",[10,1268,1269,1272],{},[13,1270,1271],{},"refreshOnLoad"," is the key. openpyxl cannot rebuild the cache itself — it has no calculation engine — but it can set the flag that makes Excel do it the moment a reader opens the workbook. From the reader's point of view the pivot is simply current.",[10,1274,1275,1276,1280],{},"Two constraints to respect. Keep the template's structure intact: renaming the data sheet or the table breaks the pivot's source reference, and openpyxl will not warn you. And write into the existing sheet rather than replacing it — the fill-only discipline described in ",[23,1277,1279],{"href":1278},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fpopulate-excel-template-without-losing-formatting\u002F","populating an Excel template without losing formatting"," applies exactly.",[172,1282,1284],{"id":1283},"step-3-the-xlwings-route","Step 3 — The xlwings route",[10,1286,1287],{},"On Windows with Excel installed, you can build the pivot programmatically through COM:",[177,1289,1291],{"className":226,"code":1290,"language":228,"meta":182,"style":182},"import xlwings as xw\n\ndef build_pivot(path, dest, data_sheet=\"Data\", pivot_sheet=\"Pivot\"):\n    \"\"\"Create a real pivot table by driving Excel. Windows only.\"\"\"\n    app = xw.App(visible=False)\n    try:\n        book = app.books.open(path)\n        source = book.sheets[data_sheet]\n        used = source.used_range\n\n        if pivot_sheet in [s.name for s in book.sheets]:\n            book.sheets[pivot_sheet].delete()\n        target = book.sheets.add(pivot_sheet, after=source)\n\n        cache = book.api.PivotCaches().Create(\n            SourceType=1,                       # xlDatabase\n            SourceData=used.api,\n        )\n        table = cache.CreatePivotTable(\n            TableDestination=target.range(\"A3\").api,\n            TableName=\"RevenuePivot\",\n        )\n\n        table.PivotFields(\"region\").Orientation = 1      # xlRowField\n        table.PivotFields(\"product\").Orientation = 2     # xlColumnField\n        revenue = table.PivotFields(\"revenue\")\n        revenue.Orientation = 4                          # xlDataField\n        revenue.Function = -4157                         # xlSum\n        revenue.NumberFormat = \"#,##0.00\"\n\n        book.save(dest)\n        return dest\n    finally:\n        app.quit()\n",[13,1292,1293,1305,1309,1333,1338,1357,1364,1374,1384,1394,1398,1421,1426,1444,1448,1458,1474,1484,1488,1498,1514,1526,1530,1534,1552,1568,1583,1596,1613,1624,1629,1635,1643,1651],{"__ignoreMap":182},[186,1294,1295,1297,1300,1302],{"class":188,"line":189},[186,1296,236],{"class":235},[186,1298,1299],{"class":239}," xlwings ",[186,1301,243],{"class":235},[186,1303,1304],{"class":239}," xw\n",[186,1306,1307],{"class":188,"line":209},[186,1308,252],{"emptyLinePlaceholder":251},[186,1310,1311,1313,1316,1319,1321,1323,1326,1328,1331],{"class":188,"line":255},[186,1312,520],{"class":235},[186,1314,1315],{"class":523}," build_pivot",[186,1317,1318],{"class":239},"(path, dest, data_sheet",[186,1320,261],{"class":235},[186,1322,532],{"class":196},[186,1324,1325],{"class":239},", pivot_sheet",[186,1327,261],{"class":235},[186,1329,1330],{"class":196},"\"Pivot\"",[186,1332,543],{"class":239},[186,1334,1335],{"class":188,"line":267},[186,1336,1337],{"class":196},"    \"\"\"Create a real pivot table by driving Excel. Windows only.\"\"\"\n",[186,1339,1340,1343,1345,1348,1351,1353,1355],{"class":188,"line":276},[186,1341,1342],{"class":239},"    app ",[186,1344,261],{"class":235},[186,1346,1347],{"class":239}," xw.App(",[186,1349,1350],{"class":559},"visible",[186,1352,261],{"class":235},[186,1354,606],{"class":321},[186,1356,618],{"class":239},[186,1358,1359,1362],{"class":188,"line":304},[186,1360,1361],{"class":235},"    try",[186,1363,1050],{"class":239},[186,1365,1366,1369,1371],{"class":188,"line":325},[186,1367,1368],{"class":239},"        book ",[186,1370,261],{"class":235},[186,1372,1373],{"class":239}," app.books.open(path)\n",[186,1375,1376,1379,1381],{"class":188,"line":331},[186,1377,1378],{"class":239},"        source ",[186,1380,261],{"class":235},[186,1382,1383],{"class":239}," book.sheets[data_sheet]\n",[186,1385,1386,1389,1391],{"class":188,"line":374},[186,1387,1388],{"class":239},"        used ",[186,1390,261],{"class":235},[186,1392,1393],{"class":239}," source.used_range\n",[186,1395,1396],{"class":188,"line":409},[186,1397,252],{"emptyLinePlaceholder":251},[186,1399,1400,1403,1406,1408,1411,1413,1416,1418],{"class":188,"line":447},[186,1401,1402],{"class":235},"        if",[186,1404,1405],{"class":239}," pivot_sheet ",[186,1407,711],{"class":235},[186,1409,1410],{"class":239}," [s.name ",[186,1412,705],{"class":235},[186,1414,1415],{"class":239}," s ",[186,1417,711],{"class":235},[186,1419,1420],{"class":239}," book.sheets]:\n",[186,1422,1423],{"class":188,"line":481},[186,1424,1425],{"class":239},"            book.sheets[pivot_sheet].delete()\n",[186,1427,1428,1431,1433,1436,1439,1441],{"class":188,"line":675},[186,1429,1430],{"class":239},"        target ",[186,1432,261],{"class":235},[186,1434,1435],{"class":239}," book.sheets.add(pivot_sheet, ",[186,1437,1438],{"class":559},"after",[186,1440,261],{"class":235},[186,1442,1443],{"class":239},"source)\n",[186,1445,1446],{"class":188,"line":684},[186,1447,252],{"emptyLinePlaceholder":251},[186,1449,1450,1453,1455],{"class":188,"line":717},[186,1451,1452],{"class":239},"        cache ",[186,1454,261],{"class":235},[186,1456,1457],{"class":239}," book.api.PivotCaches().Create(\n",[186,1459,1460,1463,1465,1468,1471],{"class":188,"line":730},[186,1461,1462],{"class":559},"            SourceType",[186,1464,261],{"class":235},[186,1466,1467],{"class":321},"1",[186,1469,1470],{"class":239},",                       ",[186,1472,1473],{"class":219},"# xlDatabase\n",[186,1475,1476,1479,1481],{"class":188,"line":736},[186,1477,1478],{"class":559},"            SourceData",[186,1480,261],{"class":235},[186,1482,1483],{"class":239},"used.api,\n",[186,1485,1486],{"class":188,"line":742},[186,1487,739],{"class":239},[186,1489,1490,1493,1495],{"class":188,"line":758},[186,1491,1492],{"class":239},"        table ",[186,1494,261],{"class":235},[186,1496,1497],{"class":239}," cache.CreatePivotTable(\n",[186,1499,1500,1503,1505,1508,1511],{"class":188,"line":772},[186,1501,1502],{"class":559},"            TableDestination",[186,1504,261],{"class":235},[186,1506,1507],{"class":239},"target.range(",[186,1509,1510],{"class":196},"\"A3\"",[186,1512,1513],{"class":239},").api,\n",[186,1515,1516,1519,1521,1524],{"class":188,"line":786},[186,1517,1518],{"class":559},"            TableName",[186,1520,261],{"class":235},[186,1522,1523],{"class":196},"\"RevenuePivot\"",[186,1525,301],{"class":239},[186,1527,1528],{"class":188,"line":803},[186,1529,739],{"class":239},[186,1531,1532],{"class":188,"line":812},[186,1533,252],{"emptyLinePlaceholder":251},[186,1535,1536,1539,1542,1545,1547,1549],{"class":188,"line":817},[186,1537,1538],{"class":239},"        table.PivotFields(",[186,1540,1541],{"class":196},"\"region\"",[186,1543,1544],{"class":239},").Orientation ",[186,1546,261],{"class":235},[186,1548,665],{"class":321},[186,1550,1551],{"class":219},"      # xlRowField\n",[186,1553,1554,1556,1559,1561,1563,1565],{"class":188,"line":1250},[186,1555,1538],{"class":239},[186,1557,1558],{"class":196},"\"product\"",[186,1560,1544],{"class":239},[186,1562,261],{"class":235},[186,1564,369],{"class":321},[186,1566,1567],{"class":219},"     # xlColumnField\n",[186,1569,1570,1573,1575,1578,1581],{"class":188,"line":1255},[186,1571,1572],{"class":239},"        revenue ",[186,1574,261],{"class":235},[186,1576,1577],{"class":239}," table.PivotFields(",[186,1579,1580],{"class":196},"\"revenue\"",[186,1582,618],{"class":239},[186,1584,1585,1588,1590,1593],{"class":188,"line":1261},[186,1586,1587],{"class":239},"        revenue.Orientation ",[186,1589,261],{"class":235},[186,1591,1592],{"class":321}," 4",[186,1594,1595],{"class":219},"                          # xlDataField\n",[186,1597,1599,1602,1604,1607,1610],{"class":188,"line":1598},28,[186,1600,1601],{"class":239},"        revenue.Function ",[186,1603,261],{"class":235},[186,1605,1606],{"class":235}," -",[186,1608,1609],{"class":321},"4157",[186,1611,1612],{"class":219},"                         # xlSum\n",[186,1614,1616,1619,1621],{"class":188,"line":1615},29,[186,1617,1618],{"class":239},"        revenue.NumberFormat ",[186,1620,261],{"class":235},[186,1622,1623],{"class":196}," \"#,##0.00\"\n",[186,1625,1627],{"class":188,"line":1626},30,[186,1628,252],{"emptyLinePlaceholder":251},[186,1630,1632],{"class":188,"line":1631},31,[186,1633,1634],{"class":239},"        book.save(dest)\n",[186,1636,1638,1641],{"class":188,"line":1637},32,[186,1639,1640],{"class":235},"        return",[186,1642,1266],{"class":239},[186,1644,1646,1649],{"class":188,"line":1645},33,[186,1647,1648],{"class":235},"    finally",[186,1650,1050],{"class":239},[186,1652,1654],{"class":188,"line":1653},34,[186,1655,1656],{"class":239},"        app.quit()\n",[10,1658,1659,1660,1663,1664,27],{},"The ",[13,1661,1662],{},"finally"," block is not optional. An unhandled exception without it leaves an invisible Excel process running and holding the file, and a scheduled job that fails a few times accumulates them until the machine runs out of memory. The wider xlwings model is in ",[23,1665,1667],{"href":1666},"\u002Fgetting-started-with-python-excel-automation\u002Fautomating-excel-with-xlwings-basics\u002Fread-and-write-a-live-excel-workbook-with-xlwings\u002F","reading and writing a live Excel workbook with xlwings",[10,1669,1670,1671,1673,1674,1676,1677,1680,1681,1684],{},"Those magic numbers are Excel's own enumeration constants — ",[13,1672,1467],{}," for a row field, ",[13,1675,1058],{}," for a column field, ",[13,1678,1679],{},"4"," for a data field, ",[13,1682,1683],{},"-4157"," for sum. They are stable across versions, but naming them makes the code readable:",[177,1686,1688],{"className":226,"code":1687,"language":228,"meta":182,"style":182},"ROW_FIELD, COLUMN_FIELD, DATA_FIELD = 1, 2, 4\nSUM, COUNT, AVERAGE = -4157, -4112, -4106\n",[13,1689,1690,1719],{"__ignoreMap":182},[186,1691,1692,1695,1697,1700,1702,1705,1708,1710,1712,1714,1716],{"class":188,"line":189},[186,1693,1694],{"class":321},"ROW_FIELD",[186,1696,285],{"class":239},[186,1698,1699],{"class":321},"COLUMN_FIELD",[186,1701,285],{"class":239},[186,1703,1704],{"class":321},"DATA_FIELD",[186,1706,1707],{"class":235}," =",[186,1709,665],{"class":321},[186,1711,285],{"class":239},[186,1713,1058],{"class":321},[186,1715,285],{"class":239},[186,1717,1718],{"class":321},"4\n",[186,1720,1721,1724,1726,1729,1731,1734,1736,1738,1740,1742,1744,1747,1749,1751],{"class":188,"line":209},[186,1722,1723],{"class":321},"SUM",[186,1725,285],{"class":239},[186,1727,1728],{"class":321},"COUNT",[186,1730,285],{"class":239},[186,1732,1733],{"class":321},"AVERAGE",[186,1735,1707],{"class":235},[186,1737,1606],{"class":235},[186,1739,1609],{"class":321},[186,1741,285],{"class":239},[186,1743,662],{"class":235},[186,1745,1746],{"class":321},"4112",[186,1748,285],{"class":239},[186,1750,662],{"class":235},[186,1752,1753],{"class":321},"4106\n",[172,1755,1757],{"id":1756},"step-4-the-portable-static-route","Step 4 — The portable static route",[10,1759,1760],{},"When the job runs on Linux, or you simply do not want a dependency on Excel, produce a formatted static pivot alongside the raw data. The reader loses interactivity and gains a report that works everywhere:",[29,1762,39,1768,39,1771,39,1774,39,1777,39,1781,39,1786,39,1790,39,1796,39,1799,39,1802,39,1805,39,1808,39,1812,39,1817,39,1819,39,1822,39,1824,39,1827,39,1831,39,1836,39,1840,39,1843,39,1847,39,1850,39,1853,39,1855,39,1858,39,1860,39,1863],{"viewBox":1763,"role":32,"ariaLabel":1764,"ariaLabelledBy":1765,"xmlns":37,"style":38},"0 0 800 228","A two-sheet workbook: a formatted static pivot for reading and the full source table for anyone who wants to build their own pivot.",[1766,1767],"static-t","static-d",[41,1769,1770],{"id":1766},"Ship the summary and the source together",[45,1772,1773],{"id":1767},"A workbook with two sheets. The Summary sheet holds a formatted static pivot with regions down the side, products across the top, totals, and currency formatting — enough for most readers. The Data sheet holds the complete source rows as a named Excel table, so a reader who wants a live pivot can insert one themselves in two clicks. This combination is the portable answer that costs nothing on a server.",[49,1775],{"x":51,"y":51,"width":52,"height":1776,"fill":54},"228",[49,1778],{"x":64,"y":1779,"width":1780,"height":861,"rx":64,"fill":112,"stroke":113,"style":70},"30","368",[56,1782,1785],{"x":1783,"y":1784,"style":116},"198","58","sheet 1 · Summary",[49,1787],{"x":1788,"y":1789,"width":79,"height":59,"rx":1679,"fill":54,"stroke":113},"44","72",[56,1791,1795],{"x":1792,"y":1793,"style":1794},"94","90","font-size:10px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","region",[49,1797],{"x":1798,"y":1789,"width":79,"height":59,"rx":1679,"fill":54,"stroke":113},"148",[56,1800,1801],{"x":1783,"y":1793,"style":1794},"Gadget",[49,1803],{"x":1804,"y":1789,"width":79,"height":59,"rx":1679,"fill":54,"stroke":113},"252",[56,1806,1807],{"x":126,"y":1793,"style":1794},"Widget",[49,1809],{"x":1788,"y":1810,"width":79,"height":1811,"rx":1679,"fill":54,"stroke":863},"102","24",[56,1813,1816],{"x":1792,"y":1814,"style":1815},"119","font-size:10px;fill:var(--text,#172033);text-anchor:middle","North",[49,1818],{"x":1798,"y":1810,"width":79,"height":1811,"rx":1679,"fill":54,"stroke":863},[56,1820,1821],{"x":1783,"y":1814,"style":1815},"7,530.80",[49,1823],{"x":1804,"y":1810,"width":79,"height":1811,"rx":1679,"fill":54,"stroke":863},[56,1825,1826],{"x":126,"y":1814,"style":1815},"8,130.10",[49,1828],{"x":1788,"y":1829,"width":1830,"height":1811,"rx":1679,"fill":68,"stroke":69,"style":70},"130","308",[56,1832,1835],{"x":1783,"y":1833,"style":1834},"147","font-size:10px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Total · 41,614.15",[56,1837,1839],{"x":1783,"y":1838,"style":107},"184","enough for most readers",[49,1841],{"x":1842,"y":1779,"width":1780,"height":861,"rx":64,"fill":68,"stroke":69,"style":70},"418",[56,1844,1846],{"x":1845,"y":1784,"style":75},"602","sheet 2 · Data",[49,1848],{"x":1849,"y":1789,"width":1830,"height":59,"rx":1679,"fill":54,"stroke":69},"448",[56,1851,1852],{"x":1845,"y":1793,"style":1834},"SalesData — a named Excel table",[49,1854],{"x":1849,"y":1810,"width":1830,"height":1811,"rx":1679,"fill":54,"stroke":863},[56,1856,1857],{"x":1845,"y":1814,"style":1815},"every source row, filterable",[49,1859],{"x":1849,"y":1829,"width":1830,"height":1811,"rx":1679,"fill":54,"stroke":863},[56,1861,1862],{"x":1845,"y":1833,"style":1815},"grows with the data",[56,1864,1865],{"x":1845,"y":1838,"style":95},"a reader can insert their own pivot in two clicks",[177,1867,1869],{"className":226,"code":1868,"language":228,"meta":182,"style":182},"import pandas as pd\n\ndef static_pivot_workbook(df, path):\n    \"\"\"A formatted summary plus the full source table — portable everywhere.\"\"\"\n    pivot = pd.pivot_table(\n        df, index=\"region\", columns=\"product\", values=\"revenue\",\n        aggfunc=\"sum\", margins=True, margins_name=\"Total\",\n    ).round(2)\n\n    with pd.ExcelWriter(path, engine=\"xlsxwriter\",\n                        datetime_format=\"yyyy-mm-dd\") as writer:\n        pivot.to_excel(writer, sheet_name=\"Summary\")\n        df.to_excel(writer, sheet_name=\"Data\", index=False)\n\n        book = writer.book\n        money = book.add_format({\"num_format\": \"#,##0.00\"})\n        header = book.add_format({\"bold\": True, \"bg_color\": \"#EEF2FF\",\n                                  \"border\": 1})\n        total = book.add_format({\"bold\": True, \"num_format\": \"#,##0.00\",\n                                 \"top\": 1})\n\n        summary = writer.sheets[\"Summary\"]\n        summary.set_column(\"A:A\", 16)\n        summary.set_column(1, len(pivot.columns), 15, money)\n        summary.set_row(0, None, header)\n        summary.set_row(len(pivot), None, total)\n        summary.freeze_panes(1, 1)\n\n        data = writer.sheets[\"Data\"]\n        data.add_table(0, 0, len(df), len(df.columns) - 1,\n                       {\"name\": \"SalesData\",\n                        \"columns\": [{\"header\": str(c)} for c in df.columns],\n                        \"style\": \"Table Style Medium 2\"})\n        data.set_column(\"A:C\", 14)\n        data.set_column(\"D:D\", 14, money)\n\n    return path\n\nstatic_pivot_workbook(sales, \"sales_summary.xlsx\")\n",[13,1870,1871,1881,1885,1895,1900,1910,1941,1973,1982,1986,2000,2014,2028,2048,2052,2061,2079,2107,2118,2143,2154,2158,2173,2187,2206,2221,2235,2248,2252,2265,2292,2306,2329,2340,2354,2367,2372,2379,2384],{"__ignoreMap":182},[186,1872,1873,1875,1877,1879],{"class":188,"line":189},[186,1874,236],{"class":235},[186,1876,240],{"class":239},[186,1878,243],{"class":235},[186,1880,246],{"class":239},[186,1882,1883],{"class":188,"line":209},[186,1884,252],{"emptyLinePlaceholder":251},[186,1886,1887,1889,1892],{"class":188,"line":255},[186,1888,520],{"class":235},[186,1890,1891],{"class":523}," static_pivot_workbook",[186,1893,1894],{"class":239},"(df, path):\n",[186,1896,1897],{"class":188,"line":267},[186,1898,1899],{"class":196},"    \"\"\"A formatted summary plus the full source table — portable everywhere.\"\"\"\n",[186,1901,1902,1905,1907],{"class":188,"line":276},[186,1903,1904],{"class":239},"    pivot ",[186,1906,261],{"class":235},[186,1908,1909],{"class":239}," pd.pivot_table(\n",[186,1911,1912,1915,1917,1919,1921,1923,1926,1928,1930,1932,1935,1937,1939],{"class":188,"line":304},[186,1913,1914],{"class":239},"        df, ",[186,1916,601],{"class":559},[186,1918,261],{"class":235},[186,1920,1541],{"class":196},[186,1922,285],{"class":239},[186,1924,1925],{"class":559},"columns",[186,1927,261],{"class":235},[186,1929,1558],{"class":196},[186,1931,285],{"class":239},[186,1933,1934],{"class":559},"values",[186,1936,261],{"class":235},[186,1938,1580],{"class":196},[186,1940,301],{"class":239},[186,1942,1943,1946,1948,1951,1953,1956,1958,1961,1963,1966,1968,1971],{"class":188,"line":325},[186,1944,1945],{"class":559},"        aggfunc",[186,1947,261],{"class":235},[186,1949,1950],{"class":196},"\"sum\"",[186,1952,285],{"class":239},[186,1954,1955],{"class":559},"margins",[186,1957,261],{"class":235},[186,1959,1960],{"class":321},"True",[186,1962,285],{"class":239},[186,1964,1965],{"class":559},"margins_name",[186,1967,261],{"class":235},[186,1969,1970],{"class":196},"\"Total\"",[186,1972,301],{"class":239},[186,1974,1975,1978,1980],{"class":188,"line":331},[186,1976,1977],{"class":239},"    ).round(",[186,1979,1058],{"class":321},[186,1981,618],{"class":239},[186,1983,1984],{"class":188,"line":374},[186,1985,252],{"emptyLinePlaceholder":251},[186,1987,1988,1990,1992,1994,1996,1998],{"class":188,"line":409},[186,1989,553],{"class":235},[186,1991,556],{"class":239},[186,1993,560],{"class":559},[186,1995,261],{"class":235},[186,1997,565],{"class":196},[186,1999,301],{"class":239},[186,2001,2002,2004,2006,2008,2010,2012],{"class":188,"line":447},[186,2003,572],{"class":559},[186,2005,261],{"class":235},[186,2007,577],{"class":196},[186,2009,580],{"class":239},[186,2011,243],{"class":235},[186,2013,585],{"class":239},[186,2015,2016,2019,2021,2023,2026],{"class":188,"line":481},[186,2017,2018],{"class":239},"        pivot.to_excel(writer, ",[186,2020,593],{"class":559},[186,2022,261],{"class":235},[186,2024,2025],{"class":196},"\"Summary\"",[186,2027,618],{"class":239},[186,2029,2030,2032,2034,2036,2038,2040,2042,2044,2046],{"class":188,"line":675},[186,2031,590],{"class":239},[186,2033,593],{"class":559},[186,2035,261],{"class":235},[186,2037,532],{"class":196},[186,2039,285],{"class":239},[186,2041,601],{"class":559},[186,2043,261],{"class":235},[186,2045,606],{"class":321},[186,2047,618],{"class":239},[186,2049,2050],{"class":188,"line":684},[186,2051,252],{"emptyLinePlaceholder":251},[186,2053,2054,2056,2058],{"class":188,"line":717},[186,2055,1368],{"class":239},[186,2057,261],{"class":235},[186,2059,2060],{"class":239}," writer.book\n",[186,2062,2063,2066,2068,2071,2073,2075,2077],{"class":188,"line":730},[186,2064,2065],{"class":239},"        money ",[186,2067,261],{"class":235},[186,2069,2070],{"class":239}," book.add_format({",[186,2072,792],{"class":196},[186,2074,696],{"class":239},[186,2076,797],{"class":196},[186,2078,484],{"class":239},[186,2080,2081,2084,2086,2088,2091,2093,2095,2097,2100,2102,2105],{"class":188,"line":736},[186,2082,2083],{"class":239},"        header ",[186,2085,261],{"class":235},[186,2087,2070],{"class":239},[186,2089,2090],{"class":196},"\"bold\"",[186,2092,696],{"class":239},[186,2094,1960],{"class":321},[186,2096,285],{"class":239},[186,2098,2099],{"class":196},"\"bg_color\"",[186,2101,696],{"class":239},[186,2103,2104],{"class":196},"\"#EEF2FF\"",[186,2106,301],{"class":239},[186,2108,2109,2112,2114,2116],{"class":188,"line":742},[186,2110,2111],{"class":196},"                                  \"border\"",[186,2113,696],{"class":239},[186,2115,1467],{"class":321},[186,2117,484],{"class":239},[186,2119,2120,2123,2125,2127,2129,2131,2133,2135,2137,2139,2141],{"class":188,"line":758},[186,2121,2122],{"class":239},"        total ",[186,2124,261],{"class":235},[186,2126,2070],{"class":239},[186,2128,2090],{"class":196},[186,2130,696],{"class":239},[186,2132,1960],{"class":321},[186,2134,285],{"class":239},[186,2136,792],{"class":196},[186,2138,696],{"class":239},[186,2140,797],{"class":196},[186,2142,301],{"class":239},[186,2144,2145,2148,2150,2152],{"class":188,"line":772},[186,2146,2147],{"class":196},"                                 \"top\"",[186,2149,696],{"class":239},[186,2151,1467],{"class":321},[186,2153,484],{"class":239},[186,2155,2156],{"class":188,"line":786},[186,2157,252],{"emptyLinePlaceholder":251},[186,2159,2160,2163,2165,2168,2170],{"class":188,"line":803},[186,2161,2162],{"class":239},"        summary ",[186,2164,261],{"class":235},[186,2166,2167],{"class":239}," writer.sheets[",[186,2169,2025],{"class":196},[186,2171,2172],{"class":239},"]\n",[186,2174,2175,2178,2180,2182,2185],{"class":188,"line":812},[186,2176,2177],{"class":239},"        summary.set_column(",[186,2179,748],{"class":196},[186,2181,285],{"class":239},[186,2183,2184],{"class":321},"16",[186,2186,618],{"class":239},[186,2188,2189,2191,2193,2195,2197,2200,2203],{"class":188,"line":817},[186,2190,2177],{"class":239},[186,2192,1467],{"class":321},[186,2194,285],{"class":239},[186,2196,651],{"class":321},[186,2198,2199],{"class":239},"(pivot.columns), ",[186,2201,2202],{"class":321},"15",[186,2204,2205],{"class":239},", money)\n",[186,2207,2208,2211,2213,2215,2218],{"class":188,"line":1250},[186,2209,2210],{"class":239},"        summary.set_row(",[186,2212,51],{"class":321},[186,2214,285],{"class":239},[186,2216,2217],{"class":321},"None",[186,2219,2220],{"class":239},", header)\n",[186,2222,2223,2225,2227,2230,2232],{"class":188,"line":1255},[186,2224,2210],{"class":239},[186,2226,651],{"class":321},[186,2228,2229],{"class":239},"(pivot), ",[186,2231,2217],{"class":321},[186,2233,2234],{"class":239},", total)\n",[186,2236,2237,2240,2242,2244,2246],{"class":188,"line":1261},[186,2238,2239],{"class":239},"        summary.freeze_panes(",[186,2241,1467],{"class":321},[186,2243,285],{"class":239},[186,2245,1467],{"class":321},[186,2247,618],{"class":239},[186,2249,2250],{"class":188,"line":1598},[186,2251,252],{"emptyLinePlaceholder":251},[186,2253,2254,2257,2259,2261,2263],{"class":188,"line":1615},[186,2255,2256],{"class":239},"        data ",[186,2258,261],{"class":235},[186,2260,2167],{"class":239},[186,2262,532],{"class":196},[186,2264,2172],{"class":239},[186,2266,2267,2270,2272,2274,2276,2278,2280,2282,2284,2286,2288,2290],{"class":188,"line":1626},[186,2268,2269],{"class":239},"        data.add_table(",[186,2271,51],{"class":321},[186,2273,285],{"class":239},[186,2275,51],{"class":321},[186,2277,285],{"class":239},[186,2279,651],{"class":321},[186,2281,654],{"class":239},[186,2283,651],{"class":321},[186,2285,659],{"class":239},[186,2287,662],{"class":235},[186,2289,665],{"class":321},[186,2291,301],{"class":239},[186,2293,2294,2297,2300,2302,2304],{"class":188,"line":1631},[186,2295,2296],{"class":239},"                       {",[186,2298,2299],{"class":196},"\"name\"",[186,2301,696],{"class":239},[186,2303,540],{"class":196},[186,2305,301],{"class":239},[186,2307,2308,2311,2313,2315,2317,2319,2321,2323,2325,2327],{"class":188,"line":1637},[186,2309,2310],{"class":196},"                        \"columns\"",[186,2312,690],{"class":239},[186,2314,693],{"class":196},[186,2316,696],{"class":239},[186,2318,699],{"class":321},[186,2320,702],{"class":239},[186,2322,705],{"class":235},[186,2324,708],{"class":239},[186,2326,711],{"class":235},[186,2328,714],{"class":239},[186,2330,2331,2334,2336,2338],{"class":188,"line":1645},[186,2332,2333],{"class":196},"                        \"style\"",[186,2335,696],{"class":239},[186,2337,725],{"class":196},[186,2339,484],{"class":239},[186,2341,2342,2345,2348,2350,2352],{"class":188,"line":1653},[186,2343,2344],{"class":239},"        data.set_column(",[186,2346,2347],{"class":196},"\"A:C\"",[186,2349,285],{"class":239},[186,2351,64],{"class":321},[186,2353,618],{"class":239},[186,2355,2357,2359,2361,2363,2365],{"class":188,"line":2356},35,[186,2358,2344],{"class":239},[186,2360,777],{"class":196},[186,2362,285],{"class":239},[186,2364,64],{"class":321},[186,2366,2205],{"class":239},[186,2368,2370],{"class":188,"line":2369},36,[186,2371,252],{"emptyLinePlaceholder":251},[186,2373,2375,2377],{"class":188,"line":2374},37,[186,2376,806],{"class":235},[186,2378,809],{"class":239},[186,2380,2382],{"class":188,"line":2381},38,[186,2383,252],{"emptyLinePlaceholder":251},[186,2385,2387,2390,2393],{"class":188,"line":2386},39,[186,2388,2389],{"class":239},"static_pivot_workbook(sales, ",[186,2391,2392],{"class":196},"\"sales_summary.xlsx\"",[186,2394,618],{"class":239},[10,2396,2397,2398,27],{},"Shipping both sheets is the point. The summary answers the question most readers have, and the table lets the one reader who wants to slice it differently insert their own pivot without asking you for a new report. The pandas pivot mechanics are covered in ",[23,2399,2401],{"href":2400},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","creating a pivot table from Excel with pandas",[172,2403,2405],{"id":2404},"common-pitfalls-and-fixes","Common pitfalls and fixes",[2407,2408,2409,2425],"table",{},[2410,2411,2412],"thead",{},[2413,2414,2415,2419,2422],"tr",{},[2416,2417,2418],"th",{},"Symptom",[2416,2420,2421],{},"Cause",[2416,2423,2424],{},"Fix",[2426,2427,2428,2440,2454,2465,2483,2494,2517],"tbody",{},[2413,2429,2430,2434,2437],{},[2431,2432,2433],"td",{},"No way to create a pivot in openpyxl",[2431,2435,2436],{},"It cannot build the cache",[2431,2438,2439],{},"Use a template, xlwings, or a static pivot.",[2413,2441,2442,2445,2448],{},[2431,2443,2444],{},"Pivot shows stale numbers",[2431,2446,2447],{},"Cache not refreshed",[2431,2449,2450,2451,27],{},"Set ",[13,2452,2453],{},"pivot.cache.refreshOnLoad = True",[2413,2455,2456,2459,2462],{},[2431,2457,2458],{},"Pivot misses the newest rows",[2431,2460,2461],{},"Source is a fixed range",[2431,2463,2464],{},"Point it at a named table and grow the ref.",[2413,2466,2467,2470,2477],{},[2431,2468,2469],{},"Excel processes accumulate",[2431,2471,2472,2473,2476],{},"xlwings ",[13,2474,2475],{},"App"," not quit on error",[2431,2478,2479,2480,2482],{},"Quit in a ",[13,2481,1662],{}," block.",[2413,2484,2485,2488,2491],{},[2431,2486,2487],{},"Pivot source reference broken",[2431,2489,2490],{},"Data sheet or table renamed",[2431,2492,2493],{},"Keep the template's structure fixed.",[2413,2495,2496,2506,2514],{},[2431,2497,2498,2501,2502,2505],{},[13,2499,2500],{},"pandas"," pivot has a ",[13,2503,2504],{},"MultiIndex"," header",[2431,2507,2508,2509,2511,2512],{},"Multiple ",[13,2510,1934],{}," or ",[13,2513,1925],{},[2431,2515,2516],{},"Flatten before writing, or accept two header rows.",[2413,2518,2519,2522,2525],{},[2431,2520,2521],{},"Works locally, fails in the container",[2431,2523,2524],{},"xlwings needs Windows and Excel",[2431,2526,2527],{},"Use the static or template route.",[172,2529,2531],{"id":2530},"performance-and-scale-notes","Performance and scale notes",[10,2533,2534],{},"The three routes have very different cost profiles, and the difference is not subtle.",[10,2536,2537,2540],{},[493,2538,2539],{},"The static route is pure pandas"," — one vectorised aggregation and one write, so a million source rows summarise in seconds. It is also the only route with no external process.",[10,2542,2543,2546,2547,2551],{},[493,2544,2545],{},"The template route"," costs one openpyxl load and save of the whole workbook. That is fine for tens of thousands of rows and slow beyond that, because openpyxl holds everything in memory. Where the source is genuinely large, write the data sheet with the streaming approach in ",[23,2548,2550],{"href":2549},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fwrite-large-dataframes-to-excel-with-write-only-mode\u002F","writing large DataFrames with write-only mode"," — though note that streaming mode cannot carry an existing pivot, so the pivot has to live in a separate workbook that references the data one.",[10,2553,2554,2557],{},[493,2555,2556],{},"The xlwings route is the slowest by a wide margin",", because every property assignment is a COM round trip. Two mitigations matter:",[177,2559,2561],{"className":226,"code":2560,"language":228,"meta":182,"style":182},"import xlwings as xw\n\napp = xw.App(visible=False)\napp.screen_updating = False       # do not repaint after every change\napp.display_alerts = False        # no modal dialogs to block a batch job\ntry:\n    ...\nfinally:\n    app.screen_updating = True\n    app.quit()\n",[13,2562,2563,2573,2577,2594,2607,2619,2626,2631,2637,2646],{"__ignoreMap":182},[186,2564,2565,2567,2569,2571],{"class":188,"line":189},[186,2566,236],{"class":235},[186,2568,1299],{"class":239},[186,2570,243],{"class":235},[186,2572,1304],{"class":239},[186,2574,2575],{"class":188,"line":209},[186,2576,252],{"emptyLinePlaceholder":251},[186,2578,2579,2582,2584,2586,2588,2590,2592],{"class":188,"line":255},[186,2580,2581],{"class":239},"app ",[186,2583,261],{"class":235},[186,2585,1347],{"class":239},[186,2587,1350],{"class":559},[186,2589,261],{"class":235},[186,2591,606],{"class":321},[186,2593,618],{"class":239},[186,2595,2596,2599,2601,2604],{"class":188,"line":267},[186,2597,2598],{"class":239},"app.screen_updating ",[186,2600,261],{"class":235},[186,2602,2603],{"class":321}," False",[186,2605,2606],{"class":219},"       # do not repaint after every change\n",[186,2608,2609,2612,2614,2616],{"class":188,"line":276},[186,2610,2611],{"class":239},"app.display_alerts ",[186,2613,261],{"class":235},[186,2615,2603],{"class":321},[186,2617,2618],{"class":219},"        # no modal dialogs to block a batch job\n",[186,2620,2621,2624],{"class":188,"line":304},[186,2622,2623],{"class":235},"try",[186,2625,1050],{"class":239},[186,2627,2628],{"class":188,"line":325},[186,2629,2630],{"class":321},"    ...\n",[186,2632,2633,2635],{"class":188,"line":331},[186,2634,1662],{"class":235},[186,2636,1050],{"class":239},[186,2638,2639,2642,2644],{"class":188,"line":374},[186,2640,2641],{"class":239},"    app.screen_updating ",[186,2643,261],{"class":235},[186,2645,1247],{"class":321},[186,2647,2648],{"class":188,"line":409},[186,2649,2650],{"class":239},"    app.quit()\n",[10,2652,2653,2654,2657],{},"And write the source data with pandas or xlsxwriter ",[18,2655,2656],{},"before"," opening Excel, rather than assigning cell values through COM — pushing a hundred thousand rows across the boundary one range at a time is orders of magnitude slower than writing the file and opening it.",[10,2659,2660],{},"The practical conclusion for most reporting pipelines: build the template once by hand, and let the scheduled job take the template route. It gives readers a live pivot, runs on a server, and costs a single workbook round trip.",[172,2662,2664],{"id":2663},"conclusion","Conclusion",[10,2666,2667,2668,2670],{},"openpyxl cannot create a pivot table from nothing, so the question is which of three routes fits your constraints. A template that already contains the pivot is usually the best answer: the script writes rows, grows the table reference, and sets ",[13,2669,1271],{}," so Excel rebuilds the cache when a reader opens the file — interactive for the reader, portable for the server. xlwings builds a pivot from scratch but ties the job to Windows with Excel. And where neither applies, ship a formatted static pivot alongside the full source as a named table, so anyone who wants to slice it differently can insert their own.",[172,2672,2674],{"id":2673},"frequently-asked-questions","Frequently asked questions",[10,2676,2677,2680],{},[493,2678,2679],{},"Can openpyxl create a pivot table from scratch?","\nNo. openpyxl can read and preserve a pivot table that already exists in a workbook, and it can mark the cache to refresh on open, but it cannot construct a new pivot definition and its cache from nothing.",[10,2682,2683,2686],{},[493,2684,2685],{},"What is the difference between a native pivot and a pandas pivot?","\nA native pivot stays interactive — the reader can drag fields, change the aggregation and refresh against new data. A pandas pivot is a static grid of values, which is fine for a printed report and useless to somebody who wants to explore.",[10,2688,2689,2692],{},[493,2690,2691],{},"Does the xlwings approach work on a server?","\nNo. It drives a real Excel instance through COM, so it needs Windows with Excel installed. On Linux or in a container you must use the template or static approaches instead.",[10,2694,2695,2698,2699,2701],{},[493,2696,2697],{},"How do I make a pivot refresh when the file opens?","\nSet the pivot cache's ",[13,2700,1271],{}," flag. openpyxl can do this on an existing pivot, so a template carrying the pivot picks up new source rows the moment a reader opens the workbook.",[10,2703,2704,2707],{},[493,2705,2706],{},"Should the source data be a table or a plain range?","\nA named Excel table. A table's reference grows automatically as rows are added, so the pivot's source never needs updating; a fixed range has to be rewritten every time the row count changes.",[172,2709,2711],{"id":2710},"related","Related",[2713,2714,2715,2722,2728,2735,2741],"ul",{},[2716,2717,2718,2719,2721],"li",{},"Up to the parent: ",[23,2720,26],{"href":25}," — the pandas side of pivoting.",[2716,2723,2724,2727],{},[23,2725,2726],{"href":2400},"Create a Pivot Table from Excel with pandas"," — the aggregation behind the static route.",[2716,2729,2730,2734],{},[23,2731,2733],{"href":2732},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted\u002F","Export a pandas Pivot Table to Excel, Formatted"," — making the static grid presentable.",[2716,2736,2737,2740],{},[23,2738,2739],{"href":835},"Create an Excel Table with Python"," — the named table a pivot should point at.",[2716,2742,2743,2746],{},[23,2744,2745],{"href":1278},"Populate an Excel Template Without Losing Formatting"," — the fill-only discipline the template route needs.",[2748,2749,2750],"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 pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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":182,"searchDepth":209,"depth":209,"links":2752},[2753,2754,2755,2756,2757,2758,2759,2760,2761,2762],{"id":174,"depth":209,"text":175},{"id":487,"depth":209,"text":488},{"id":839,"depth":209,"text":840},{"id":1283,"depth":209,"text":1284},{"id":1756,"depth":209,"text":1757},{"id":2404,"depth":209,"text":2405},{"id":2530,"depth":209,"text":2531},{"id":2663,"depth":209,"text":2664},{"id":2673,"depth":209,"text":2674},{"id":2710,"depth":209,"text":2711},"2026-08-15","Ship a real, refreshable Excel pivot table rather than a static grid — write the source as a table, drive Excel through xlwings on Windows, and the cross-platform alternatives.","md",[2767,2769,2771,2773,2775],{"q":2679,"a":2768},"No. openpyxl can read and preserve a pivot table that already exists in a workbook, and it can mark the cache to refresh on open, but it cannot construct a new pivot definition and its cache from nothing.",{"q":2685,"a":2770},"A native pivot stays interactive — the reader can drag fields, change the aggregation and refresh against new data. A pandas pivot is a static grid of values, which is fine for a printed report and useless to somebody who wants to explore.",{"q":2691,"a":2772},"No. It drives a real Excel instance through COM, so it needs Windows with Excel installed. On Linux or in a container you must use the template or static approaches instead.",{"q":2697,"a":2774},"Set the pivot cache's refreshOnLoad flag. openpyxl can do this on an existing pivot, so a template carrying the pivot picks up new source rows the moment a reader opens the workbook.",{"q":2706,"a":2776},"A named Excel table. A table's reference grows automatically as rows are added, so the pivot's source never needs updating; a fixed range has to be rewritten every time the row count changes.",{},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python",{"title":2780,"description":2781},"Create a Real Excel Pivot Table from Python","Why openpyxl cannot create a pivot table from scratch, how to build one with xlwings on Windows, and the portable alternative — a formatted static pivot plus a named table.","add-a-native-excel-pivot-table-with-python","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python\u002Findex","how-to","uC9wY9omqGnt7Pf0FrKjxvrburUm7tTBxSVYDLKO2hk",[2787,2791],{"title":2788,"path":2789,"stem":2790,"children":-1},"Creating Pivot Tables from Excel Data with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Findex",{"title":2792,"path":2793,"stem":2794,"children":-1},"Create a Pivot Table from Excel with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002Findex",1786800028575]