[{"data":1,"prerenderedAt":1580},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted":1571},{"id":4,"title":5,"body":6,"dateModified":1548,"datePublished":1548,"description":1549,"extension":1550,"faq":1551,"meta":1562,"navigation":290,"path":1563,"seo":1564,"slug":1567,"stem":1568,"type":1569,"__hash__":1570},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted\u002Findex.md","Export a Pandas Pivot Table to Excel (Formatted)",{"type":7,"value":8,"toc":1536},"minimark",[9,32,37,65,68,247,251,262,591,612,616,644,769,782,786,845,855,859,862,1228,1241,1245,1370,1381,1385,1396,1400,1420,1438,1453,1467,1476,1480,1498,1502,1532],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"To export a pandas pivot table to a clean, formatted Excel file, build the table with ",[14,15,16],"code",{},"pivot_table()",", flatten any ",[14,19,20],{},"MultiIndex"," on the columns and index so it writes as a flat grid, save it with ",[14,23,24],{},"to_excel()",", then reopen the file with openpyxl to apply a styled header and number formats. The flattening step is what keeps the output from exporting as messy stacked headers. This page is part of the ",[27,28,30],"a",{"href":29},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002F","Creating Pivot Tables from Excel Data"," cluster.",[33,34,36],"h2",{"id":35},"prerequisites","Prerequisites",[38,39,44],"pre",{"className":40,"code":41,"language":42,"meta":43,"style":43},"language-bash shiki shiki-themes github-light github-dark","pip install pandas openpyxl\n","bash","",[14,45,46],{"__ignoreMap":43},[47,48,51,55,59,62],"span",{"class":49,"line":50},"line",1,[47,52,54],{"class":53},"sScJk","pip",[47,56,58],{"class":57},"sZZnC"," install",[47,60,61],{"class":57}," pandas",[47,63,64],{"class":57}," openpyxl\n",[10,66,67],{},"Every block below runs in order against the sample data built first.",[69,70,78,79,78,83,78,87,78,94,78,104,78,111,78,116,78,119,78,123,78,127,78,130,78,135,78,139,78,146,78,150,78,157,78,163,78,168,78,172,78,175,78,179,78,184,78,187,78,193,78,198,78,202,78,206,78,209,78,212,78,216,78,219,78,222,78,225,78,228,78,231,78,235,78,239,78,242],"svg",{"viewBox":71,"role":72,"ariaLabelledBy":73,"xmlns":76,"style":77},"0 0 760 240","img",[74,75],"exportpivot-t","exportpivot-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[80,81,82],"title",{"id":74},"From a MultiIndex pivot to a formatted Excel sheet",[84,85,86],"desc",{"id":75},"A pivot with stacked MultiIndex column headers is flattened into single-line headers, then exported and styled in openpyxl with a colored header row, number formats, and a grand-total row.",[88,89,93],"text",{"x":90,"y":91,"style":92},"118","26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","MultiIndex pivot",[95,96],"rect",{"x":97,"y":98,"width":99,"height":100,"rx":101,"fill":102,"stroke":103},"28","44","180","150","10","var(--brand-soft,rgba(91,92,240,0.12))","var(--line,#cdd5e6)",[95,105],{"x":106,"y":107,"width":108,"height":109,"fill":110,"stroke":103},"40","56","156","22","var(--surface-muted,#eef2ff)",[88,112,115],{"x":90,"y":113,"style":114},"71","font-size:11px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","Revenue | Revenue",[95,117],{"x":106,"y":118,"width":108,"height":109,"fill":110,"stroke":103},"78",[88,120,122],{"x":90,"y":121,"style":114},"93","Jan     |     Feb",[49,124],{"x1":106,"y1":125,"x2":126,"y2":125,"stroke":103},"124","196",[49,128],{"x1":106,"y1":129,"x2":126,"y2":129,"stroke":103},"148",[88,131,134],{"x":132,"y":90,"style":133},"60","font-size:11px;fill:var(--text,#172033)","North",[88,136,138],{"x":132,"y":137,"style":133},"142","South",[49,140],{"x1":141,"y1":142,"x2":143,"y2":142,"stroke":144,"style":145},"216","119","262","var(--brand,#5b5cf0)","stroke-width:2px",[147,148],"polygon",{"points":149,"fill":144},"262,119 252,114 252,124",[95,151],{"x":152,"y":153,"width":90,"height":154,"rx":155,"fill":144,"stroke":156},"266","92","54","11","var(--brand-strong,#4338ca)",[88,158,162],{"x":159,"y":160,"style":161},"325","115","font-size:12px;font-weight:700;fill:#ffffff;text-anchor:middle","flatten",[88,164,167],{"x":159,"y":165,"style":166},"133","font-size:10.5px;fill:rgba(255,255,255,0.9);text-anchor:middle","+ openpyxl",[49,169],{"x1":170,"y1":142,"x2":171,"y2":142,"stroke":144,"style":145},"388","434",[147,173],{"points":174,"fill":144},"434,119 424,114 424,124",[88,176,178],{"x":177,"y":91,"style":92},"595","Formatted Excel sheet",[95,180],{"x":181,"y":98,"width":182,"height":100,"rx":101,"fill":183,"stroke":103},"440","290","#ffffff",[95,185],{"x":181,"y":98,"width":182,"height":97,"rx":186,"fill":144},"0",[88,188,192],{"x":189,"y":190,"style":191},"468","63","font-size:11.5px;font-weight:700;fill:#ffffff","Region",[88,194,197],{"x":195,"y":190,"style":196},"588","font-size:11.5px;font-weight:700;fill:#ffffff;text-anchor:middle","Jan",[88,199,201],{"x":200,"y":190,"style":196},"688","Feb",[49,203],{"x1":181,"y1":204,"x2":205,"y2":204,"stroke":103},"100","730",[49,207],{"x1":181,"y1":208,"x2":205,"y2":208,"stroke":103},"128",[88,210,134],{"x":189,"y":153,"style":211},"font-size:11.5px;fill:var(--text,#172033)",[88,213,215],{"x":195,"y":153,"style":214},"font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","$12,000",[88,217,218],{"x":200,"y":153,"style":214},"$9,000",[88,220,138],{"x":189,"y":221,"style":211},"120",[88,223,224],{"x":195,"y":221,"style":214},"$8,000",[88,226,227],{"x":200,"y":221,"style":214},"$7,500",[95,229],{"x":181,"y":108,"width":182,"height":230,"fill":110},"38",[88,232,234],{"x":189,"y":99,"style":233},"font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca)","Total",[88,236,238],{"x":195,"y":99,"style":237},"font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","$20,000",[88,240,241],{"x":200,"y":99,"style":237},"$16,500",[88,243,246],{"x":177,"y":244,"style":245},"220","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","styled header · number formats · grand-total row",[33,248,250],{"id":249},"build-the-pivot-table-with-a-grand-total","Build the pivot table with a grand total",[10,252,253,254,257,258,261],{},"Set ",[14,255,256],{},"margins=True"," to append a grand-total row and column. ",[14,259,260],{},"margins_name"," controls the label so it reads cleanly:",[38,263,267],{"className":264,"code":265,"language":266,"meta":43,"style":43},"language-python shiki shiki-themes github-light github-dark","import pandas as pd\n\ndf = pd.DataFrame({\n    \"Region\": [\"East\", \"East\", \"West\", \"West\", \"East\", \"West\"],\n    \"Product\": [\"Widget\", \"Gadget\", \"Widget\", \"Gadget\", \"Widget\", \"Widget\"],\n    \"Quarter\": [\"Q1\", \"Q1\", \"Q1\", \"Q2\", \"Q2\", \"Q2\"],\n    \"Sales\": [1200.5, 800.0, 950.25, 1100.0, 1350.75, 700.0],\n})\n\npivot = pd.pivot_table(\n    df,\n    index=[\"Region\", \"Product\"],\n    columns=\"Quarter\",\n    values=\"Sales\",\n    aggfunc=\"sum\",\n    fill_value=0,\n    margins=True,\n    margins_name=\"Total\",\n)\nprint(pivot)\n","python",[14,268,269,285,292,304,341,375,409,448,454,459,470,476,498,512,525,538,550,563,576,582],{"__ignoreMap":43},[47,270,271,275,279,282],{"class":49,"line":50},[47,272,274],{"class":273},"szBVR","import",[47,276,278],{"class":277},"sVt8B"," pandas ",[47,280,281],{"class":273},"as",[47,283,284],{"class":277}," pd\n",[47,286,288],{"class":49,"line":287},2,[47,289,291],{"emptyLinePlaceholder":290},true,"\n",[47,293,295,298,301],{"class":49,"line":294},3,[47,296,297],{"class":277},"df ",[47,299,300],{"class":273},"=",[47,302,303],{"class":277}," pd.DataFrame({\n",[47,305,307,310,313,316,319,321,323,326,328,330,332,334,336,338],{"class":49,"line":306},4,[47,308,309],{"class":57},"    \"Region\"",[47,311,312],{"class":277},": [",[47,314,315],{"class":57},"\"East\"",[47,317,318],{"class":277},", ",[47,320,315],{"class":57},[47,322,318],{"class":277},[47,324,325],{"class":57},"\"West\"",[47,327,318],{"class":277},[47,329,325],{"class":57},[47,331,318],{"class":277},[47,333,315],{"class":57},[47,335,318],{"class":277},[47,337,325],{"class":57},[47,339,340],{"class":277},"],\n",[47,342,344,347,349,352,354,357,359,361,363,365,367,369,371,373],{"class":49,"line":343},5,[47,345,346],{"class":57},"    \"Product\"",[47,348,312],{"class":277},[47,350,351],{"class":57},"\"Widget\"",[47,353,318],{"class":277},[47,355,356],{"class":57},"\"Gadget\"",[47,358,318],{"class":277},[47,360,351],{"class":57},[47,362,318],{"class":277},[47,364,356],{"class":57},[47,366,318],{"class":277},[47,368,351],{"class":57},[47,370,318],{"class":277},[47,372,351],{"class":57},[47,374,340],{"class":277},[47,376,378,381,383,386,388,390,392,394,396,399,401,403,405,407],{"class":49,"line":377},6,[47,379,380],{"class":57},"    \"Quarter\"",[47,382,312],{"class":277},[47,384,385],{"class":57},"\"Q1\"",[47,387,318],{"class":277},[47,389,385],{"class":57},[47,391,318],{"class":277},[47,393,385],{"class":57},[47,395,318],{"class":277},[47,397,398],{"class":57},"\"Q2\"",[47,400,318],{"class":277},[47,402,398],{"class":57},[47,404,318],{"class":277},[47,406,398],{"class":57},[47,408,340],{"class":277},[47,410,412,415,417,421,423,426,428,431,433,436,438,441,443,446],{"class":49,"line":411},7,[47,413,414],{"class":57},"    \"Sales\"",[47,416,312],{"class":277},[47,418,420],{"class":419},"sj4cs","1200.5",[47,422,318],{"class":277},[47,424,425],{"class":419},"800.0",[47,427,318],{"class":277},[47,429,430],{"class":419},"950.25",[47,432,318],{"class":277},[47,434,435],{"class":419},"1100.0",[47,437,318],{"class":277},[47,439,440],{"class":419},"1350.75",[47,442,318],{"class":277},[47,444,445],{"class":419},"700.0",[47,447,340],{"class":277},[47,449,451],{"class":49,"line":450},8,[47,452,453],{"class":277},"})\n",[47,455,457],{"class":49,"line":456},9,[47,458,291],{"emptyLinePlaceholder":290},[47,460,462,465,467],{"class":49,"line":461},10,[47,463,464],{"class":277},"pivot ",[47,466,300],{"class":273},[47,468,469],{"class":277}," pd.pivot_table(\n",[47,471,473],{"class":49,"line":472},11,[47,474,475],{"class":277},"    df,\n",[47,477,479,483,485,488,491,493,496],{"class":49,"line":478},12,[47,480,482],{"class":481},"s4XuR","    index",[47,484,300],{"class":273},[47,486,487],{"class":277},"[",[47,489,490],{"class":57},"\"Region\"",[47,492,318],{"class":277},[47,494,495],{"class":57},"\"Product\"",[47,497,340],{"class":277},[47,499,501,504,506,509],{"class":49,"line":500},13,[47,502,503],{"class":481},"    columns",[47,505,300],{"class":273},[47,507,508],{"class":57},"\"Quarter\"",[47,510,511],{"class":277},",\n",[47,513,515,518,520,523],{"class":49,"line":514},14,[47,516,517],{"class":481},"    values",[47,519,300],{"class":273},[47,521,522],{"class":57},"\"Sales\"",[47,524,511],{"class":277},[47,526,528,531,533,536],{"class":49,"line":527},15,[47,529,530],{"class":481},"    aggfunc",[47,532,300],{"class":273},[47,534,535],{"class":57},"\"sum\"",[47,537,511],{"class":277},[47,539,541,544,546,548],{"class":49,"line":540},16,[47,542,543],{"class":481},"    fill_value",[47,545,300],{"class":273},[47,547,186],{"class":419},[47,549,511],{"class":277},[47,551,553,556,558,561],{"class":49,"line":552},17,[47,554,555],{"class":481},"    margins",[47,557,300],{"class":273},[47,559,560],{"class":419},"True",[47,562,511],{"class":277},[47,564,566,569,571,574],{"class":49,"line":565},18,[47,567,568],{"class":481},"    margins_name",[47,570,300],{"class":273},[47,572,573],{"class":57},"\"Total\"",[47,575,511],{"class":277},[47,577,579],{"class":49,"line":578},19,[47,580,581],{"class":277},")\n",[47,583,585,588],{"class":49,"line":584},20,[47,586,587],{"class":419},"print",[47,589,590],{"class":277},"(pivot)\n",[10,592,593,594,596,597,318,599,602,603,318,606,318,609,611],{},"This produces a ",[14,595,20],{}," on the rows (",[14,598,192],{},[14,600,601],{},"Product",") and named columns (",[14,604,605],{},"Q1",[14,607,608],{},"Q2",[14,610,234],{},"). Written as-is, the row index spans two header levels and the column index name leaks into the corner cell.",[33,613,615],{"id":614},"flatten-the-index-and-columns","Flatten the index and columns",[10,617,618,621,622,624,625,629,630,632,633,636,637,639,640,643],{},[14,619,620],{},"reset_index()"," turns the row ",[14,623,20],{}," into regular columns. If the ",[626,627,628],"strong",{},"columns"," are also a ",[14,631,20],{}," (which happens when you pass multiple ",[14,634,635],{},"values"," or stacked ",[14,638,628],{},"), flatten them with ",[14,641,642],{},"to_flat_index()"," and join the tuples into readable strings:",[38,645,647],{"className":264,"code":646,"language":266,"meta":43,"style":43},"flat = pivot.reset_index()\n\n# Flatten MultiIndex columns if present\nif isinstance(flat.columns, pd.MultiIndex):\n    flat.columns = [\"_\".join(str(c) for c in col if c != \"\").strip(\"_\")\n                    for col in flat.columns.to_flat_index()]\n\n# Drop the leftover \"Quarter\" columns-axis name\nflat.columns.name = None\nprint(flat)\n",[14,648,649,659,663,669,680,731,743,747,752,762],{"__ignoreMap":43},[47,650,651,654,656],{"class":49,"line":50},[47,652,653],{"class":277},"flat ",[47,655,300],{"class":273},[47,657,658],{"class":277}," pivot.reset_index()\n",[47,660,661],{"class":49,"line":287},[47,662,291],{"emptyLinePlaceholder":290},[47,664,665],{"class":49,"line":294},[47,666,668],{"class":667},"sJ8bj","# Flatten MultiIndex columns if present\n",[47,670,671,674,677],{"class":49,"line":306},[47,672,673],{"class":273},"if",[47,675,676],{"class":419}," isinstance",[47,678,679],{"class":277},"(flat.columns, pd.MultiIndex):\n",[47,681,682,685,687,690,693,696,699,702,705,708,711,714,716,718,721,724,727,729],{"class":49,"line":343},[47,683,684],{"class":277},"    flat.columns ",[47,686,300],{"class":273},[47,688,689],{"class":277}," [",[47,691,692],{"class":57},"\"_\"",[47,694,695],{"class":277},".join(",[47,697,698],{"class":419},"str",[47,700,701],{"class":277},"(c) ",[47,703,704],{"class":273},"for",[47,706,707],{"class":277}," c ",[47,709,710],{"class":273},"in",[47,712,713],{"class":277}," col ",[47,715,673],{"class":273},[47,717,707],{"class":277},[47,719,720],{"class":273},"!=",[47,722,723],{"class":57}," \"\"",[47,725,726],{"class":277},").strip(",[47,728,692],{"class":57},[47,730,581],{"class":277},[47,732,733,736,738,740],{"class":49,"line":377},[47,734,735],{"class":273},"                    for",[47,737,713],{"class":277},[47,739,710],{"class":273},[47,741,742],{"class":277}," flat.columns.to_flat_index()]\n",[47,744,745],{"class":49,"line":411},[47,746,291],{"emptyLinePlaceholder":290},[47,748,749],{"class":49,"line":450},[47,750,751],{"class":667},"# Drop the leftover \"Quarter\" columns-axis name\n",[47,753,754,757,759],{"class":49,"line":456},[47,755,756],{"class":277},"flat.columns.name ",[47,758,300],{"class":273},[47,760,761],{"class":419}," None\n",[47,763,764,766],{"class":49,"line":461},[47,765,587],{"class":419},[47,767,768],{"class":277},"(flat)\n",[10,770,771,772,774,775,778,779,781],{},"For this example the columns are single-level, so ",[14,773,620],{}," plus clearing ",[14,776,777],{},"columns.name"," is enough. Keep the ",[14,780,20],{}," branch in your pipeline so it stays correct when the shape changes.",[33,783,785],{"id":784},"write-the-flattened-table-to-excel","Write the flattened table to Excel",[38,787,789],{"className":264,"code":788,"language":266,"meta":43,"style":43},"flat.to_excel(\"pivot_report.xlsx\", index=False, sheet_name=\"Summary\",\n              engine=\"openpyxl\")\nprint(\"Wrote pivot_report.xlsx\")\n",[14,790,791,821,833],{"__ignoreMap":43},[47,792,793,796,799,801,804,806,809,811,814,816,819],{"class":49,"line":50},[47,794,795],{"class":277},"flat.to_excel(",[47,797,798],{"class":57},"\"pivot_report.xlsx\"",[47,800,318],{"class":277},[47,802,803],{"class":481},"index",[47,805,300],{"class":273},[47,807,808],{"class":419},"False",[47,810,318],{"class":277},[47,812,813],{"class":481},"sheet_name",[47,815,300],{"class":273},[47,817,818],{"class":57},"\"Summary\"",[47,820,511],{"class":277},[47,822,823,826,828,831],{"class":49,"line":287},[47,824,825],{"class":481},"              engine",[47,827,300],{"class":273},[47,829,830],{"class":57},"\"openpyxl\"",[47,832,581],{"class":277},[47,834,835,837,840,843],{"class":49,"line":294},[47,836,587],{"class":419},[47,838,839],{"class":277},"(",[47,841,842],{"class":57},"\"Wrote pivot_report.xlsx\"",[47,844,581],{"class":277},[10,846,847,848,851,852,854],{},"Pass ",[14,849,850],{},"index=False"," because the index is already a real column after ",[14,853,620],{}," — otherwise you get a duplicate, unnamed first column.",[33,856,858],{"id":857},"style-the-header-and-apply-number-formats","Style the header and apply number formats",[10,860,861],{},"pandas writes values but not formatting. Reopen the saved file with openpyxl to bold the header, fill it, and apply a currency number format to the numeric columns:",[38,863,865],{"className":264,"code":864,"language":266,"meta":43,"style":43},"from openpyxl import load_workbook\nfrom openpyxl.styles import Font, PatternFill, Alignment\n\nwb = load_workbook(\"pivot_report.xlsx\")\nws = wb[\"Summary\"]\n\nheader_fill = PatternFill(\"solid\", fgColor=\"1F4E78\")\nheader_font = Font(bold=True, color=\"FFFFFF\")\n\nfor cell in ws[1]:\n    cell.fill = header_fill\n    cell.font = header_font\n    cell.alignment = Alignment(horizontal=\"center\")\n\n# Currency format on every numeric column (skip the two text key columns)\nfor row in ws.iter_rows(min_row=2, min_col=3):\n    for cell in row:\n        cell.number_format = '#,##0.00'\n\n# Bold the grand-total row (last row)\nfor cell in ws[ws.max_row]:\n    cell.font = Font(bold=True)\n\nws.column_dimensions[\"A\"].width = 12\nws.column_dimensions[\"B\"].width = 12\n\nwb.save(\"pivot_report.xlsx\")\nprint(f\"Formatted {ws.max_row - 1} data rows\")\n",[14,866,867,880,892,896,910,925,929,954,983,987,1005,1015,1025,1045,1049,1054,1087,1099,1109,1113,1118,1130,1147,1152,1169,1183,1188,1198],{"__ignoreMap":43},[47,868,869,872,875,877],{"class":49,"line":50},[47,870,871],{"class":273},"from",[47,873,874],{"class":277}," openpyxl ",[47,876,274],{"class":273},[47,878,879],{"class":277}," load_workbook\n",[47,881,882,884,887,889],{"class":49,"line":287},[47,883,871],{"class":273},[47,885,886],{"class":277}," openpyxl.styles ",[47,888,274],{"class":273},[47,890,891],{"class":277}," Font, PatternFill, Alignment\n",[47,893,894],{"class":49,"line":294},[47,895,291],{"emptyLinePlaceholder":290},[47,897,898,901,903,906,908],{"class":49,"line":306},[47,899,900],{"class":277},"wb ",[47,902,300],{"class":273},[47,904,905],{"class":277}," load_workbook(",[47,907,798],{"class":57},[47,909,581],{"class":277},[47,911,912,915,917,920,922],{"class":49,"line":343},[47,913,914],{"class":277},"ws ",[47,916,300],{"class":273},[47,918,919],{"class":277}," wb[",[47,921,818],{"class":57},[47,923,924],{"class":277},"]\n",[47,926,927],{"class":49,"line":377},[47,928,291],{"emptyLinePlaceholder":290},[47,930,931,934,936,939,942,944,947,949,952],{"class":49,"line":411},[47,932,933],{"class":277},"header_fill ",[47,935,300],{"class":273},[47,937,938],{"class":277}," PatternFill(",[47,940,941],{"class":57},"\"solid\"",[47,943,318],{"class":277},[47,945,946],{"class":481},"fgColor",[47,948,300],{"class":273},[47,950,951],{"class":57},"\"1F4E78\"",[47,953,581],{"class":277},[47,955,956,959,961,964,967,969,971,973,976,978,981],{"class":49,"line":450},[47,957,958],{"class":277},"header_font ",[47,960,300],{"class":273},[47,962,963],{"class":277}," Font(",[47,965,966],{"class":481},"bold",[47,968,300],{"class":273},[47,970,560],{"class":419},[47,972,318],{"class":277},[47,974,975],{"class":481},"color",[47,977,300],{"class":273},[47,979,980],{"class":57},"\"FFFFFF\"",[47,982,581],{"class":277},[47,984,985],{"class":49,"line":456},[47,986,291],{"emptyLinePlaceholder":290},[47,988,989,991,994,996,999,1002],{"class":49,"line":461},[47,990,704],{"class":273},[47,992,993],{"class":277}," cell ",[47,995,710],{"class":273},[47,997,998],{"class":277}," ws[",[47,1000,1001],{"class":419},"1",[47,1003,1004],{"class":277},"]:\n",[47,1006,1007,1010,1012],{"class":49,"line":472},[47,1008,1009],{"class":277},"    cell.fill ",[47,1011,300],{"class":273},[47,1013,1014],{"class":277}," header_fill\n",[47,1016,1017,1020,1022],{"class":49,"line":478},[47,1018,1019],{"class":277},"    cell.font ",[47,1021,300],{"class":273},[47,1023,1024],{"class":277}," header_font\n",[47,1026,1027,1030,1032,1035,1038,1040,1043],{"class":49,"line":500},[47,1028,1029],{"class":277},"    cell.alignment ",[47,1031,300],{"class":273},[47,1033,1034],{"class":277}," Alignment(",[47,1036,1037],{"class":481},"horizontal",[47,1039,300],{"class":273},[47,1041,1042],{"class":57},"\"center\"",[47,1044,581],{"class":277},[47,1046,1047],{"class":49,"line":514},[47,1048,291],{"emptyLinePlaceholder":290},[47,1050,1051],{"class":49,"line":527},[47,1052,1053],{"class":667},"# Currency format on every numeric column (skip the two text key columns)\n",[47,1055,1056,1058,1061,1063,1066,1069,1071,1074,1076,1079,1081,1084],{"class":49,"line":540},[47,1057,704],{"class":273},[47,1059,1060],{"class":277}," row ",[47,1062,710],{"class":273},[47,1064,1065],{"class":277}," ws.iter_rows(",[47,1067,1068],{"class":481},"min_row",[47,1070,300],{"class":273},[47,1072,1073],{"class":419},"2",[47,1075,318],{"class":277},[47,1077,1078],{"class":481},"min_col",[47,1080,300],{"class":273},[47,1082,1083],{"class":419},"3",[47,1085,1086],{"class":277},"):\n",[47,1088,1089,1092,1094,1096],{"class":49,"line":552},[47,1090,1091],{"class":273},"    for",[47,1093,993],{"class":277},[47,1095,710],{"class":273},[47,1097,1098],{"class":277}," row:\n",[47,1100,1101,1104,1106],{"class":49,"line":565},[47,1102,1103],{"class":277},"        cell.number_format ",[47,1105,300],{"class":273},[47,1107,1108],{"class":57}," '#,##0.00'\n",[47,1110,1111],{"class":49,"line":578},[47,1112,291],{"emptyLinePlaceholder":290},[47,1114,1115],{"class":49,"line":584},[47,1116,1117],{"class":667},"# Bold the grand-total row (last row)\n",[47,1119,1121,1123,1125,1127],{"class":49,"line":1120},21,[47,1122,704],{"class":273},[47,1124,993],{"class":277},[47,1126,710],{"class":273},[47,1128,1129],{"class":277}," ws[ws.max_row]:\n",[47,1131,1133,1135,1137,1139,1141,1143,1145],{"class":49,"line":1132},22,[47,1134,1019],{"class":277},[47,1136,300],{"class":273},[47,1138,963],{"class":277},[47,1140,966],{"class":481},[47,1142,300],{"class":273},[47,1144,560],{"class":419},[47,1146,581],{"class":277},[47,1148,1150],{"class":49,"line":1149},23,[47,1151,291],{"emptyLinePlaceholder":290},[47,1153,1155,1158,1161,1164,1166],{"class":49,"line":1154},24,[47,1156,1157],{"class":277},"ws.column_dimensions[",[47,1159,1160],{"class":57},"\"A\"",[47,1162,1163],{"class":277},"].width ",[47,1165,300],{"class":273},[47,1167,1168],{"class":419}," 12\n",[47,1170,1172,1174,1177,1179,1181],{"class":49,"line":1171},25,[47,1173,1157],{"class":277},[47,1175,1176],{"class":57},"\"B\"",[47,1178,1163],{"class":277},[47,1180,300],{"class":273},[47,1182,1168],{"class":419},[47,1184,1186],{"class":49,"line":1185},26,[47,1187,291],{"emptyLinePlaceholder":290},[47,1189,1191,1194,1196],{"class":49,"line":1190},27,[47,1192,1193],{"class":277},"wb.save(",[47,1195,798],{"class":57},[47,1197,581],{"class":277},[47,1199,1201,1203,1205,1208,1211,1214,1217,1220,1223,1226],{"class":49,"line":1200},28,[47,1202,587],{"class":419},[47,1204,839],{"class":277},[47,1206,1207],{"class":273},"f",[47,1209,1210],{"class":57},"\"Formatted ",[47,1212,1213],{"class":419},"{",[47,1215,1216],{"class":277},"ws.max_row ",[47,1218,1219],{"class":273},"-",[47,1221,1222],{"class":419}," 1}",[47,1224,1225],{"class":57}," data rows\"",[47,1227,581],{"class":277},[10,1229,1230,1233,1234,1237,1238,1240],{},[14,1231,1232],{},"number_format"," is a cell-level string; ",[14,1235,1236],{},"'#,##0.00'"," gives thousands separators and two decimals. Adjusting ",[14,1239,1078],{}," skips the text key columns so only the numbers get the currency format.",[33,1242,1244],{"id":1243},"common-pitfalls","Common pitfalls",[1246,1247,1248,1264],"table",{},[1249,1250,1251],"thead",{},[1252,1253,1254,1258,1261],"tr",{},[1255,1256,1257],"th",{},"Symptom",[1255,1259,1260],{},"Cause",[1255,1262,1263],{},"Fix",[1265,1266,1267,1284,1303,1320,1338,1353],"tbody",{},[1252,1268,1269,1273,1278],{},[1270,1271,1272],"td",{},"Stacked, half-empty header rows in Excel",[1270,1274,1275,1277],{},[14,1276,20],{}," columns written directly",[1270,1279,1280,1281,1283],{},"Flatten with ",[14,1282,642],{}," and join the tuples",[1252,1285,1286,1289,1295],{},[1270,1287,1288],{},"Empty extra column on the left",[1270,1290,1291,1292,1294],{},"Row ",[14,1293,20],{}," written as the index",[1270,1296,1297,1299,1300],{},[14,1298,620],{},", then ",[14,1301,1302],{},"to_excel(index=False)",[1252,1304,1305,1312,1315],{},[1270,1306,1307,1308,1311],{},"Stray ",[14,1309,1310],{},"Quarter"," label in a corner cell",[1270,1313,1314],{},"Columns-axis name survives the write",[1270,1316,1317],{},[14,1318,1319],{},"flat.columns.name = None",[1252,1321,1322,1328,1333],{},[1270,1323,1324,1325],{},"Grand total labeled ",[14,1326,1327],{},"All",[1270,1329,1330,1331],{},"Default ",[14,1332,260],{},[1270,1334,847,1335],{},[14,1336,1337],{},"margins_name=\"Total\"",[1252,1339,1340,1343,1350],{},[1270,1341,1342],{},"Formatting gone after editing in pandas",[1270,1344,1345,1346,1349],{},"Re-reading with ",[14,1347,1348],{},"read_excel"," strips styles",[1270,1351,1352],{},"Apply styles last, with openpyxl, and don't round-trip",[1252,1354,1355,1358,1364],{},[1270,1356,1357],{},"Index name lost after flatten",[1270,1359,1360,1363],{},[14,1361,1362],{},"reset_index"," consumes index names into columns",[1270,1365,1366,1367],{},"Read them from the new column headers, not ",[14,1368,1369],{},"df.index.name",[10,1371,1372,1373,1376,1377,1380],{},"The last row matters most: any time you read a formatted file back into pandas and re-write it, all openpyxl styling is discarded. Treat the openpyxl pass as the final step and never funnel a styled workbook back through ",[14,1374,1375],{},"pd.read_excel","\u002F",[14,1378,1379],{},"to_excel",".",[33,1382,1384],{"id":1383},"performance-and-scale-note","Performance and scale note",[10,1386,1387,1388,1391,1392,1395],{},"The pivot and flatten are vectorized and trivial even for large source frames; the openpyxl styling loop is the bottleneck because it touches each cell in Python. For pivots up to a few thousand rows this is instant. If you are formatting tens of thousands of rows, prefer the xlsxwriter engine with ",[14,1389,1390],{},"pd.ExcelWriter(path, engine=\"xlsxwriter\")"," and apply a single ",[14,1393,1394],{},"set_column(..., cell_format)"," per column instead of looping cells — column-level formats are far faster than per-cell assignment.",[33,1397,1399],{"id":1398},"frequently-asked-questions","Frequently asked questions",[10,1401,1402,1405,1406,1409,1410,1412,1413,1415,1416,1419],{},[626,1403,1404],{},"Why do my pivot columns export as messy stacked headers?"," Because ",[14,1407,1408],{},"pivot_table"," produced a ",[14,1411,20],{}," on the columns and ",[14,1414,1379],{}," writes each level as its own header row. Flatten with ",[14,1417,1418],{},"df.columns = df.columns.to_flat_index()"," and join the tuples into single strings before writing.",[10,1421,1422,1425,1426,1428,1429,1431,1432,1434,1435,1437],{},[626,1423,1424],{},"How do I add a grand-total row?"," Pass ",[14,1427,256],{}," to ",[14,1430,1408],{},". Use ",[14,1433,1337],{}," to rename the default ",[14,1436,1327],{}," label on both the total row and column.",[10,1439,1440,1443,1444,1446,1447,1449,1450,1380],{},[626,1441,1442],{},"Why is there a blank extra column on the left of my sheet?"," The row ",[14,1445,20],{}," was written as the spreadsheet index. Call ",[14,1448,620],{}," to turn it into real columns, then ",[14,1451,1452],{},"to_excel(..., index=False)",[10,1454,1455,1458,1459,1462,1463,1466],{},[626,1456,1457],{},"Can I format the pivot without reopening the file?"," Yes — write through ",[14,1460,1461],{},"pd.ExcelWriter"," with ",[14,1464,1465],{},"engine=\"xlsxwriter\""," and apply formats in the same context manager. The openpyxl reopen approach is simpler when the file already exists.",[10,1468,1469,1472,1473,1475],{},[626,1470,1471],{},"Why did my formatting disappear?"," You likely read the styled file back with ",[14,1474,1375],{}," and re-wrote it. pandas does not preserve cell styles, so make the openpyxl\u002Fxlsxwriter formatting your final step.",[33,1477,1479],{"id":1478},"conclusion","Conclusion",[10,1481,1482,1483,1485,1486,1462,1488,1490,1491,1494,1495,1497],{},"A clean formatted pivot export is four steps: pivot with ",[14,1484,256],{},", flatten the ",[14,1487,20],{},[14,1489,1362],{}," and ",[14,1492,1493],{},"to_flat_index",", write with ",[14,1496,850],{},", then style once with openpyxl. Keeping the flatten and style steps separate from any re-read is what guarantees a tidy, formatted result.",[33,1499,1501],{"id":1500},"where-to-go-next","Where to go next",[1503,1504,1505,1511,1518,1525],"ul",{},[1506,1507,1508,1510],"li",{},[27,1509,30],{"href":29}," — the parent cluster.",[1506,1512,1513,1517],{},[27,1514,1516],{"href":1515},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcreate-pivot-table-from-excel-with-pandas\u002F","Create a Pivot Table From Excel With Pandas"," — the pivot fundamentals behind this export.",[1506,1519,1520,1524],{},[27,1521,1523],{"href":1522},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002F","Applying Number and Date Formats in Excel"," — deeper number-format strings for the styling step.",[1506,1526,1527,1531],{},[27,1528,1530],{"href":1529},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002F","Building Multi-Sheet Excel Dashboards"," — combine several formatted pivots into one workbook.",[1533,1534,1535],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":43,"searchDepth":287,"depth":287,"links":1537},[1538,1539,1540,1541,1542,1543,1544,1545,1546,1547],{"id":35,"depth":287,"text":36},{"id":249,"depth":287,"text":250},{"id":614,"depth":287,"text":615},{"id":784,"depth":287,"text":785},{"id":857,"depth":287,"text":858},{"id":1243,"depth":287,"text":1244},{"id":1383,"depth":287,"text":1384},{"id":1398,"depth":287,"text":1399},{"id":1478,"depth":287,"text":1479},{"id":1500,"depth":287,"text":1501},"2026-06-18","Build a pandas pivot_table, flatten its MultiIndex, export with to_excel, then add a styled header, number formats, and a grand-total row with openpyxl.","md",[1552,1554,1556,1558,1560],{"q":1404,"a":1553},"Because pivot_table produced a MultiIndex on the columns and to_excel writes each level as its own header row. Flatten with df.columns = df.columns.to_flat_index() and join the tuples into single strings before writing.",{"q":1424,"a":1555},"Pass margins=True to pivot_table. Use margins_name=\"Total\" to rename the default All label on both the total row and column.",{"q":1442,"a":1557},"The row MultiIndex was written as the spreadsheet index. Call reset_index() to turn it into real columns, then to_excel(..., index=False).",{"q":1457,"a":1559},"Yes — write through pd.ExcelWriter with engine=\"xlsxwriter\" and apply formats in the same context manager. The openpyxl reopen approach is simpler when the file already exists.",{"q":1471,"a":1561},"You likely read the styled file back with pd.read_excel and re-wrote it. pandas does not preserve cell styles, so make the openpyxl\u002Fxlsxwriter formatting your final step.",{},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted",{"title":1565,"description":1566},"Export Pandas Pivot Table to Excel (Formatted)","Write a pandas pivot_table to a clean, formatted Excel file: flatten MultiIndex columns, add margins totals, then style headers and number formats with openpyxl.","export-pandas-pivot-table-to-excel-formatted","advanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fexport-pandas-pivot-table-to-excel-formatted\u002Findex","long_tail","EKBUEC8y1E74lOLX-XbKRyh7dV2U_hmTthgIjucdFco",[1572,1576],{"title":1573,"path":1574,"stem":1575,"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",{"title":1577,"path":1578,"stem":1579,"children":-1},"Handling Missing Data in Excel Reports with Pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports","advanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Findex",1781795518759]