[{"data":1,"prerenderedAt":2174},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel":2140,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel":2149,"children:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel":2154,"related:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel":2155},{"id":4,"title":5,"body":6,"dateModified":2105,"datePublished":2105,"description":2106,"extension":2107,"faq":2108,"keyword":2117,"kind":2118,"meta":2119,"navigation":425,"path":2128,"seo":2129,"slug":2132,"stem":2133,"topics":2134,"type":2138,"__hash__":2139},"docs\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel\u002Findex.md","Write a MultiIndex DataFrame to Excel with pandas",{"type":7,"value":8,"toc":2091},"minimark",[9,32,362,367,394,397,660,664,671,813,822,826,832,925,932,1092,1096,1111,1225,1233,1237,1240,1297,1317,1321,1328,1421,1435,1552,1556,1559,1930,1937,1941,1986,1990,1998,2002,2019,2023,2029,2035,2041,2054,2058,2087],[10,11,12,13,17,18,21,22,25,26,31],"p",{},"A pivot table, a ",[14,15,16],"code",{},"groupby"," over two keys, a ",[14,19,20],{},"crosstab"," with margins — the most useful summaries in pandas come out with a MultiIndex on the rows, the columns or both. Writing one to Excel mostly just works, but the result has three surprises: merged label cells, an extra near-empty row between the header and the data, and an error the moment you pass ",[14,23,24],{},"index=False",". This guide, part of ",[27,28,30],"a",{"href":29},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002F","Writing DataFrames to Excel with Pandas",", explains what pandas writes, how to choose between a presentation layout and a filterable one, and how to read the sheet back without losing the hierarchy.",[33,34,42,43,42,47,42,51,42,59,42,68,42,72,42,79,42,83,42,87,42,91,42,95,42,98,42,102,42,105,42,109,42,112,42,116,42,120,42,125,42,127,42,129,42,135,42,139,42,141,42,144,42,147,42,151,42,153,42,155,42,158,42,160,42,162,42,164,42,166,42,168,42,170,42,172,42,174,42,177,42,181,42,185,42,189,42,191,42,194,42,196,42,198,42,200,42,202,42,205,42,209,42,214,42,219,42,221,42,225,42,227,42,230,42,232,42,234,42,236,42,238,42,241,42,245,42,247,42,250,42,252,42,254,42,257,42,259,42,261,42,263,42,266,42,270,42,272,42,276,42,278,42,280,42,282,42,285,42,287,42,289,42,291,42,293,42,296,42,300,42,302,42,304,42,306,42,308,42,311,42,313,42,315,42,317,42,324,42,329,42,336,42,347,42,353,42,358],"svg",{"viewBox":35,"role":36,"ariaLabelledBy":37,"xmlns":40,"style":41},"53 0 653 252","img",[38,39],"mi-layout-t","mi-layout-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:653px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[44,45,46],"title",{"id":38},"How pandas lays out a MultiIndex DataFrame on a sheet",[48,49,50],"desc",{"id":39},"Two header rows hold the column levels revenue and units over products A and B, with the top labels merged across their columns. A third row holds the index names region and quarter. Below, region labels are merged down the rows they cover.",[52,53],"rect",{"x":54,"y":55,"width":56,"height":57,"fill":58},"53","0","653","252","#ffffff",[52,60],{"x":61,"y":62,"width":63,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"69.6","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[52,69],{"x":70,"y":62,"width":71,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"101.6","63.6",[73,74,78],"text",{"x":75,"y":76,"style":77},"133.4","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[52,80],{"x":81,"y":62,"width":82,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"165.2","72.6",[73,84,86],{"x":85,"y":76,"style":77},"201.5","B",[52,88],{"x":89,"y":62,"width":90,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"237.8","44.0",[73,92,94],{"x":93,"y":76,"style":77},"259.8","C",[52,96],{"x":97,"y":62,"width":90,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"281.8",[73,99,101],{"x":100,"y":76,"style":77},"303.8","D",[52,103],{"x":104,"y":62,"width":90,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"325.8",[73,106,108],{"x":107,"y":76,"style":77},"347.8","E",[52,110],{"x":111,"y":62,"width":90,"height":64,"rx":55,"fill":65,"stroke":66,"style":67},"369.8",[73,113,115],{"x":114,"y":76,"style":77},"391.8","F",[52,117],{"x":61,"y":118,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"40.0","28.0",[73,121,124],{"x":122,"y":123,"style":77},"85.6","59.0","1",[52,126],{"x":70,"y":118,"width":71,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,128],{"x":81,"y":118,"width":82,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,130],{"x":89,"y":118,"width":131,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},"88.0","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[73,136,138],{"x":97,"y":123,"style":137},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","revenue",[52,140],{"x":104,"y":118,"width":131,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[73,142,143],{"x":111,"y":123,"style":137},"units",[52,145],{"x":61,"y":146,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"68.0",[73,148,150],{"x":122,"y":149,"style":77},"87.0","2",[52,152],{"x":70,"y":146,"width":71,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[52,154],{"x":81,"y":146,"width":82,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[73,156,157],{"x":85,"y":149,"style":137},"product",[52,159],{"x":89,"y":146,"width":90,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[73,161,78],{"x":93,"y":149,"style":137},[52,163],{"x":97,"y":146,"width":90,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[73,165,86],{"x":100,"y":149,"style":137},[52,167],{"x":104,"y":146,"width":90,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[73,169,78],{"x":107,"y":149,"style":137},[52,171],{"x":111,"y":146,"width":90,"height":119,"rx":55,"fill":132,"stroke":133,"style":134},[73,173,86],{"x":114,"y":149,"style":137},[52,175],{"x":61,"y":176,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"96.0",[73,178,180],{"x":122,"y":179,"style":77},"115.0","3",[52,182],{"x":70,"y":176,"width":71,"height":119,"rx":55,"fill":183,"stroke":184,"style":134},"#fdefd8","var(--gold,#b4740a)",[73,186,188],{"x":75,"y":179,"style":187},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","region",[52,190],{"x":81,"y":176,"width":82,"height":119,"rx":55,"fill":183,"stroke":184,"style":134},[73,192,193],{"x":85,"y":179,"style":187},"quarter",[52,195],{"x":89,"y":176,"width":90,"height":119,"rx":55,"fill":183,"stroke":184,"style":134},[52,197],{"x":97,"y":176,"width":90,"height":119,"rx":55,"fill":183,"stroke":184,"style":134},[52,199],{"x":104,"y":176,"width":90,"height":119,"rx":55,"fill":183,"stroke":184,"style":134},[52,201],{"x":111,"y":176,"width":90,"height":119,"rx":55,"fill":183,"stroke":184,"style":134},[52,203],{"x":61,"y":204,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"124.0",[73,206,208],{"x":122,"y":207,"style":77},"143.0","4",[52,210],{"x":70,"y":204,"width":71,"height":211,"rx":55,"fill":212,"stroke":213,"style":134},"56.0","#d9f4f1","var(--teal,#0f9488)",[73,215,218],{"x":75,"y":216,"style":217},"157.0","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","North",[52,220],{"x":81,"y":204,"width":82,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,222,224],{"x":85,"y":207,"style":223},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Q1",[52,226],{"x":89,"y":204,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,228,229],{"x":93,"y":207,"style":223},"10",[52,231],{"x":97,"y":204,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,233],{"x":104,"y":204,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,235,124],{"x":107,"y":207,"style":223},[52,237],{"x":111,"y":204,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,239],{"x":61,"y":240,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"152.0",[73,242,244],{"x":122,"y":243,"style":77},"171.0","5",[52,246],{"x":81,"y":240,"width":82,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,248,249],{"x":85,"y":243,"style":223},"Q2",[52,251],{"x":89,"y":240,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,253],{"x":97,"y":240,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,255,256],{"x":100,"y":243,"style":223},"20",[52,258],{"x":104,"y":240,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,260],{"x":111,"y":240,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,262,150],{"x":114,"y":243,"style":223},[52,264],{"x":61,"y":265,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"180.0",[73,267,269],{"x":122,"y":268,"style":77},"199.0","6",[52,271],{"x":70,"y":265,"width":71,"height":211,"rx":55,"fill":212,"stroke":213,"style":134},[73,273,275],{"x":75,"y":274,"style":217},"213.0","South",[52,277],{"x":81,"y":265,"width":82,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,279,224],{"x":85,"y":268,"style":223},[52,281],{"x":89,"y":265,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,283,284],{"x":93,"y":268,"style":223},"30",[52,286],{"x":97,"y":265,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,288],{"x":104,"y":265,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,290,180],{"x":107,"y":268,"style":223},[52,292],{"x":111,"y":265,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,294],{"x":61,"y":295,"width":63,"height":119,"rx":55,"fill":65,"stroke":66,"style":67},"208.0",[73,297,299],{"x":122,"y":298,"style":77},"227.0","7",[52,301],{"x":81,"y":295,"width":82,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,303,249],{"x":85,"y":298,"style":223},[52,305],{"x":89,"y":295,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,307],{"x":97,"y":295,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,309,310],{"x":100,"y":298,"style":223},"40",[52,312],{"x":104,"y":295,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[52,314],{"x":111,"y":295,"width":90,"height":119,"rx":55,"fill":58,"stroke":66,"style":67},[73,316,208],{"x":114,"y":298,"style":223},[318,319],"line",{"x1":320,"y1":321,"x2":322,"y2":321,"stroke":133,"style":323},"421.8","126.0","453.8","stroke-width:2px",[325,326],"polygon",{"points":327,"fill":328},"457.8,126.0 448.8,131.0 448.8,121.0","#5b5cf0",[52,330],{"x":331,"y":332,"width":333,"height":334,"rx":335,"fill":183,"stroke":184,"style":323},"463.8","70.8","226.6","110.5","12",[337,338,340],"g",{"transform":339},"translate(565.1,83.8) scale(1.000)",[341,342],"path",{"d":343,"fill":344,"stroke":345,"style":346},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","none","var(--gold-ink,#7a4e06)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[73,348,352],{"x":349,"y":350,"style":351},"577.1","130.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","row 3 is the index-name row",[73,354,357],{"x":349,"y":355,"style":356},"149.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","it breaks filters and tables",[73,359,361],{"x":349,"y":360,"style":356},"167.2","merged labels break sorting",[363,364,366],"h2",{"id":365},"prerequisites","Prerequisites",[368,369,374],"pre",{"className":370,"code":371,"language":372,"meta":373,"style":373},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,375,376],{"__ignoreMap":373},[377,378,380,384,388,391],"span",{"class":318,"line":379},1,[377,381,383],{"class":382},"sMTad","pip",[377,385,387],{"class":386},"srMev"," install",[377,389,390],{"class":386}," pandas",[377,392,393],{"class":386}," openpyxl\n",[10,395,396],{},"A pivot with two levels on each axis makes a good test case:",[368,398,402],{"className":399,"code":400,"language":401,"meta":373,"style":373},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\n    \"region\": [\"North\", \"North\", \"South\", \"South\"],\n    \"quarter\": [\"Q1\", \"Q2\", \"Q1\", \"Q2\"],\n    \"product\": [\"A\", \"B\", \"A\", \"B\"],\n    \"units\": [1, 2, 3, 4],\n    \"revenue\": [10.0, 20.0, 30.0, 40.0],\n})\npivot = sales.pivot_table(index=[\"region\", \"quarter\"], columns=\"product\",\n                          values=[\"revenue\", \"units\"], aggfunc=\"sum\")\nprint(pivot)\n","python",[14,403,404,420,427,439,468,494,520,545,572,578,620,651],{"__ignoreMap":373},[377,405,406,410,414,417],{"class":318,"line":379},[377,407,409],{"class":408},"s-kum","import",[377,411,413],{"class":412},"skGVy"," pandas ",[377,415,416],{"class":408},"as",[377,418,419],{"class":412}," pd\n",[377,421,423],{"class":318,"line":422},2,[377,424,426],{"emptyLinePlaceholder":425},true,"\n",[377,428,430,433,436],{"class":318,"line":429},3,[377,431,432],{"class":412},"sales ",[377,434,435],{"class":408},"=",[377,437,438],{"class":412}," pd.DataFrame({\n",[377,440,442,445,448,451,454,456,458,461,463,465],{"class":318,"line":441},4,[377,443,444],{"class":386},"    \"region\"",[377,446,447],{"class":412},": [",[377,449,450],{"class":386},"\"North\"",[377,452,453],{"class":412},", ",[377,455,450],{"class":386},[377,457,453],{"class":412},[377,459,460],{"class":386},"\"South\"",[377,462,453],{"class":412},[377,464,460],{"class":386},[377,466,467],{"class":412},"],\n",[377,469,471,474,476,479,481,484,486,488,490,492],{"class":318,"line":470},5,[377,472,473],{"class":386},"    \"quarter\"",[377,475,447],{"class":412},[377,477,478],{"class":386},"\"Q1\"",[377,480,453],{"class":412},[377,482,483],{"class":386},"\"Q2\"",[377,485,453],{"class":412},[377,487,478],{"class":386},[377,489,453],{"class":412},[377,491,483],{"class":386},[377,493,467],{"class":412},[377,495,497,500,502,505,507,510,512,514,516,518],{"class":318,"line":496},6,[377,498,499],{"class":386},"    \"product\"",[377,501,447],{"class":412},[377,503,504],{"class":386},"\"A\"",[377,506,453],{"class":412},[377,508,509],{"class":386},"\"B\"",[377,511,453],{"class":412},[377,513,504],{"class":386},[377,515,453],{"class":412},[377,517,509],{"class":386},[377,519,467],{"class":412},[377,521,523,526,528,531,533,535,537,539,541,543],{"class":318,"line":522},7,[377,524,525],{"class":386},"    \"units\"",[377,527,447],{"class":412},[377,529,124],{"class":530},"sP0c6",[377,532,453],{"class":412},[377,534,150],{"class":530},[377,536,453],{"class":412},[377,538,180],{"class":530},[377,540,453],{"class":412},[377,542,208],{"class":530},[377,544,467],{"class":412},[377,546,548,551,553,556,558,561,563,566,568,570],{"class":318,"line":547},8,[377,549,550],{"class":386},"    \"revenue\"",[377,552,447],{"class":412},[377,554,555],{"class":530},"10.0",[377,557,453],{"class":412},[377,559,560],{"class":530},"20.0",[377,562,453],{"class":412},[377,564,565],{"class":530},"30.0",[377,567,453],{"class":412},[377,569,118],{"class":530},[377,571,467],{"class":412},[377,573,575],{"class":318,"line":574},9,[377,576,577],{"class":412},"})\n",[377,579,581,584,586,589,593,595,598,601,603,606,609,612,614,617],{"class":318,"line":580},10,[377,582,583],{"class":412},"pivot ",[377,585,435],{"class":408},[377,587,588],{"class":412}," sales.pivot_table(",[377,590,592],{"class":591},"sa561","index",[377,594,435],{"class":408},[377,596,597],{"class":412},"[",[377,599,600],{"class":386},"\"region\"",[377,602,453],{"class":412},[377,604,605],{"class":386},"\"quarter\"",[377,607,608],{"class":412},"], ",[377,610,611],{"class":591},"columns",[377,613,435],{"class":408},[377,615,616],{"class":386},"\"product\"",[377,618,619],{"class":412},",\n",[377,621,623,626,628,630,633,635,638,640,643,645,648],{"class":318,"line":622},11,[377,624,625],{"class":591},"                          values",[377,627,435],{"class":408},[377,629,597],{"class":412},[377,631,632],{"class":386},"\"revenue\"",[377,634,453],{"class":412},[377,636,637],{"class":386},"\"units\"",[377,639,608],{"class":412},[377,641,642],{"class":591},"aggfunc",[377,644,435],{"class":408},[377,646,647],{"class":386},"\"sum\"",[377,649,650],{"class":412},")\n",[377,652,654,657],{"class":318,"line":653},12,[377,655,656],{"class":530},"print",[377,658,659],{"class":412},"(pivot)\n",[363,661,663],{"id":662},"step-1-the-default-write-merged-and-readable","Step 1 — The default write: merged and readable",[10,665,666,667,670],{},"With no options, ",[14,668,669],{},"to_excel"," writes every level, merges repeated outer labels on both axes, and centres them — a layout that reads like a report:",[368,672,674],{"className":399,"code":673,"language":401,"meta":373,"style":373},"from openpyxl import load_workbook\n\npivot.to_excel(\"pivot_default.xlsx\", sheet_name=\"Pivot\")\n\nws = load_workbook(\"pivot_default.xlsx\")[\"Pivot\"]\nfor row in ws.iter_rows(max_row=4, values_only=True):\n    print(row)\nprint(sorted(str(r) for r in ws.merged_cells.ranges))\n# ['A4:A5', 'A6:A7', 'C1:D1', 'E1:F1']\n",[14,675,676,689,693,713,717,737,771,779,807],{"__ignoreMap":373},[377,677,678,681,684,686],{"class":318,"line":379},[377,679,680],{"class":408},"from",[377,682,683],{"class":412}," openpyxl ",[377,685,409],{"class":408},[377,687,688],{"class":412}," load_workbook\n",[377,690,691],{"class":318,"line":422},[377,692,426],{"emptyLinePlaceholder":425},[377,694,695,698,701,703,706,708,711],{"class":318,"line":429},[377,696,697],{"class":412},"pivot.to_excel(",[377,699,700],{"class":386},"\"pivot_default.xlsx\"",[377,702,453],{"class":412},[377,704,705],{"class":591},"sheet_name",[377,707,435],{"class":408},[377,709,710],{"class":386},"\"Pivot\"",[377,712,650],{"class":412},[377,714,715],{"class":318,"line":441},[377,716,426],{"emptyLinePlaceholder":425},[377,718,719,722,724,727,729,732,734],{"class":318,"line":470},[377,720,721],{"class":412},"ws ",[377,723,435],{"class":408},[377,725,726],{"class":412}," load_workbook(",[377,728,700],{"class":386},[377,730,731],{"class":412},")[",[377,733,710],{"class":386},[377,735,736],{"class":412},"]\n",[377,738,739,742,745,748,751,754,756,758,760,763,765,768],{"class":318,"line":496},[377,740,741],{"class":408},"for",[377,743,744],{"class":412}," row ",[377,746,747],{"class":408},"in",[377,749,750],{"class":412}," ws.iter_rows(",[377,752,753],{"class":591},"max_row",[377,755,435],{"class":408},[377,757,208],{"class":530},[377,759,453],{"class":412},[377,761,762],{"class":591},"values_only",[377,764,435],{"class":408},[377,766,767],{"class":530},"True",[377,769,770],{"class":412},"):\n",[377,772,773,776],{"class":318,"line":522},[377,774,775],{"class":530},"    print",[377,777,778],{"class":412},"(row)\n",[377,780,781,783,786,789,791,794,797,799,802,804],{"class":318,"line":547},[377,782,656],{"class":530},[377,784,785],{"class":412},"(",[377,787,788],{"class":530},"sorted",[377,790,785],{"class":412},[377,792,793],{"class":530},"str",[377,795,796],{"class":412},"(r) ",[377,798,741],{"class":408},[377,800,801],{"class":412}," r ",[377,803,747],{"class":408},[377,805,806],{"class":412}," ws.merged_cells.ranges))\n",[377,808,809],{"class":318,"line":574},[377,810,812],{"class":811},"s-wDw","# ['A4:A5', 'A6:A7', 'C1:D1', 'E1:F1']\n",[10,814,815,816,818,819,821],{},"The printout shows the structure from the diagram: two header rows for the two column levels, a third row holding the index names ",[14,817,188],{}," and ",[14,820,193],{},", then data. Row three exists because the index levels have names and pandas has nowhere else to put them. For a report someone reads, that is fine. For a sheet someone works with, it is a problem: Excel's AutoFilter and tables expect exactly one header row directly above the data, and merged cells break sorting.",[363,823,825],{"id":824},"step-2-a-filterable-layout-repeat-instead-of-merge","Step 2 — A filterable layout: repeat instead of merge",[10,827,828,831],{},[14,829,830],{},"merge_cells=False"," repeats outer labels instead of merging them. Every data row then carries its full key, so sorting and filtering keep rows intact:",[368,833,835],{"className":399,"code":834,"language":401,"meta":373,"style":373},"pivot.to_excel(\"pivot_repeated.xlsx\", sheet_name=\"Pivot\", merge_cells=False)\n\nws = load_workbook(\"pivot_repeated.xlsx\")[\"Pivot\"]\nfor row in ws.iter_rows(min_row=1, max_row=5, values_only=True):\n    print(row)\n",[14,836,837,864,868,884,919],{"__ignoreMap":373},[377,838,839,841,844,846,848,850,852,854,857,859,862],{"class":318,"line":379},[377,840,697],{"class":412},[377,842,843],{"class":386},"\"pivot_repeated.xlsx\"",[377,845,453],{"class":412},[377,847,705],{"class":591},[377,849,435],{"class":408},[377,851,710],{"class":386},[377,853,453],{"class":412},[377,855,856],{"class":591},"merge_cells",[377,858,435],{"class":408},[377,860,861],{"class":530},"False",[377,863,650],{"class":412},[377,865,866],{"class":318,"line":422},[377,867,426],{"emptyLinePlaceholder":425},[377,869,870,872,874,876,878,880,882],{"class":318,"line":429},[377,871,721],{"class":412},[377,873,435],{"class":408},[377,875,726],{"class":412},[377,877,843],{"class":386},[377,879,731],{"class":412},[377,881,710],{"class":386},[377,883,736],{"class":412},[377,885,886,888,890,892,894,897,899,901,903,905,907,909,911,913,915,917],{"class":318,"line":441},[377,887,741],{"class":408},[377,889,744],{"class":412},[377,891,747],{"class":408},[377,893,750],{"class":412},[377,895,896],{"class":591},"min_row",[377,898,435],{"class":408},[377,900,124],{"class":530},[377,902,453],{"class":412},[377,904,753],{"class":591},[377,906,435],{"class":408},[377,908,244],{"class":530},[377,910,453],{"class":412},[377,912,762],{"class":591},[377,914,435],{"class":408},[377,916,767],{"class":530},[377,918,770],{"class":412},[377,920,921,923],{"class":318,"line":470},[377,922,775],{"class":530},[377,924,778],{"class":412},[10,926,927,928,931],{},"The top column labels now read ",[14,929,930],{},"revenue, revenue, units, units"," across and the region is written on every row. The index-name row is still there, though — repeating labels does not remove it. To get a true single-row header you have to flatten.",[33,933,42,939,42,942,42,945,42,949,42,954,42,962,42,968,42,973,42,979,42,986,42,990,42,997,42,1001,42,1006,42,1009,42,1015,42,1019,42,1023,42,1027,42,1032,42,1035,42,1040,42,1043,42,1048,42,1051,42,1058,42,1063,42,1067,42,1071,42,1076,42,1079,42,1084,42,1087],{"viewBox":934,"role":36,"ariaLabelledBy":935,"xmlns":40,"style":938},"4 0 752 207",[936,937],"mi-choices-t","mi-choices-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[44,940,941],{"id":936},"Three ways to write the same MultiIndex pivot",[48,943,944],{"id":937},"The default merges labels and suits a printed report. merge_cells=False repeats labels, which keeps sorting safe but still has the index-name row. Flattening columns and resetting the index gives one header row and plain columns, which suits filtering, tables and further analysis in Excel.",[52,946],{"x":208,"y":55,"width":947,"height":948,"fill":58},"752","207",[52,950],{"x":560,"y":62,"width":951,"height":952,"rx":953,"fill":132,"stroke":133,"style":323},"228.0","175.0","14",[337,955,957],{"transform":956},"translate(121.0,29.0) scale(1.083)",[341,958],{"d":959,"fill":344,"stroke":960,"style":961},"M6 3h9l4 4v14H6z M15 3v4h4 M9 12h6 M9 15.5h6 M9 19h4","var(--brand-strong,#4338ca)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[73,963,967],{"x":964,"y":965,"style":966},"134.0","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Default (merged)",[318,969],{"x1":970,"y1":971,"x2":972,"y2":971,"stroke":133,"style":67},"36.0","91.0","232.0",[73,974,978],{"x":975,"y":976,"style":977},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","reads like a report",[337,980,982],{"transform":981},"translate(38.5,99.5) scale(0.625)",[341,983],{"d":984,"fill":344,"stroke":213,"style":985},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[73,987,989],{"x":975,"y":988,"style":977},"137.0","merged cells break sorting",[337,991,993],{"transform":992},"translate(38.5,125.5) scale(0.625)",[341,994],{"d":995,"fill":344,"stroke":996,"style":985},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[73,998,1000],{"x":975,"y":999,"style":977},"163.0","extra index-name row",[337,1002,1004],{"transform":1003},"translate(38.5,151.5) scale(0.625)",[341,1005],{"d":995,"fill":344,"stroke":996,"style":985},[52,1007],{"x":1008,"y":62,"width":951,"height":952,"rx":953,"fill":183,"stroke":184,"style":323},"266.0",[337,1010,1012],{"transform":1011},"translate(367.0,29.0) scale(1.083)",[341,1013],{"d":1014,"fill":344,"stroke":345,"style":961},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16",[73,1016,830],{"x":1017,"y":965,"style":1018},"380.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[318,1020],{"x1":1021,"y1":971,"x2":1022,"y2":971,"stroke":184,"style":67},"282.0","478.0",[73,1024,1026],{"x":1025,"y":976,"style":977},"306.0","every row has its full key",[337,1028,1030],{"transform":1029},"translate(284.5,99.5) scale(0.625)",[341,1031],{"d":984,"fill":344,"stroke":213,"style":985},[73,1033,1034],{"x":1025,"y":988,"style":977},"sorting is safe",[337,1036,1038],{"transform":1037},"translate(284.5,125.5) scale(0.625)",[341,1039],{"d":984,"fill":344,"stroke":213,"style":985},[73,1041,1042],{"x":1025,"y":999,"style":977},"still an index-name row",[337,1044,1046],{"transform":1045},"translate(284.5,151.5) scale(0.625)",[341,1047],{"d":995,"fill":344,"stroke":996,"style":985},[52,1049],{"x":1050,"y":62,"width":951,"height":952,"rx":953,"fill":212,"stroke":213,"style":323},"512.0",[337,1052,1054],{"transform":1053},"translate(613.0,29.0) scale(1.083)",[341,1055],{"d":1056,"fill":344,"stroke":1057,"style":961},"M3 5h18l-7 8v6l-4 2v-8z","var(--teal-ink,#0b6157)",[73,1059,1062],{"x":1060,"y":965,"style":1061},"626.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Flattened",[318,1064],{"x1":1065,"y1":971,"x2":1066,"y2":971,"stroke":213,"style":67},"528.0","724.0",[73,1068,1070],{"x":1069,"y":976,"style":977},"552.0","one header row",[337,1072,1074],{"transform":1073},"translate(530.5,99.5) scale(0.625)",[341,1075],{"d":984,"fill":344,"stroke":213,"style":985},[73,1077,1078],{"x":1069,"y":988,"style":977},"AutoFilter and tables work",[337,1080,1082],{"transform":1081},"translate(530.5,125.5) scale(0.625)",[341,1083],{"d":984,"fill":344,"stroke":213,"style":985},[73,1085,1086],{"x":1069,"y":999,"style":977},"loses the visual hierarchy",[337,1088,1090],{"transform":1089},"translate(530.5,151.5) scale(0.625)",[341,1091],{"d":343,"fill":344,"stroke":184,"style":985},[363,1093,1095],{"id":1094},"step-3-flatten-for-filters-tables-and-analysis","Step 3 — Flatten for filters, tables and analysis",[10,1097,1098,1099,1102,1103,1106,1107,1110],{},"Flattening means two things: join the column levels into single strings, and turn the index levels into ordinary columns with ",[14,1100,1101],{},"reset_index()",". The join rule is yours to choose; ",[14,1104,1105],{},"revenue_A"," is compact, ",[14,1108,1109],{},"Revenue (A)"," reads better in a report.",[368,1112,1114],{"className":399,"code":1113,"language":401,"meta":373,"style":373},"flat = pivot.copy()\nflat.columns = [f\"{measure}_{product}\" for measure, product in flat.columns]\nflat = flat.reset_index()\nprint(flat.columns.tolist())\n# ['region', 'quarter', 'revenue_A', 'revenue_B', 'units_A', 'units_B']\n\nflat.to_excel(\"pivot_flat.xlsx\", sheet_name=\"Pivot\", index=False)\n",[14,1115,1116,1126,1174,1183,1190,1195,1199],{"__ignoreMap":373},[377,1117,1118,1121,1123],{"class":318,"line":379},[377,1119,1120],{"class":412},"flat ",[377,1122,435],{"class":408},[377,1124,1125],{"class":412}," pivot.copy()\n",[377,1127,1128,1131,1133,1136,1139,1142,1146,1149,1152,1155,1157,1159,1161,1163,1166,1169,1171],{"class":318,"line":422},[377,1129,1130],{"class":412},"flat.columns ",[377,1132,435],{"class":408},[377,1134,1135],{"class":412}," [",[377,1137,1138],{"class":408},"f",[377,1140,1141],{"class":386},"\"",[377,1143,1145],{"class":1144},"sSjpA","{",[377,1147,1148],{"class":412},"measure",[377,1150,1151],{"class":1144},"}",[377,1153,1154],{"class":386},"_",[377,1156,1145],{"class":1144},[377,1158,157],{"class":412},[377,1160,1151],{"class":1144},[377,1162,1141],{"class":386},[377,1164,1165],{"class":408}," for",[377,1167,1168],{"class":412}," measure, product ",[377,1170,747],{"class":408},[377,1172,1173],{"class":412}," flat.columns]\n",[377,1175,1176,1178,1180],{"class":318,"line":429},[377,1177,1120],{"class":412},[377,1179,435],{"class":408},[377,1181,1182],{"class":412}," flat.reset_index()\n",[377,1184,1185,1187],{"class":318,"line":441},[377,1186,656],{"class":530},[377,1188,1189],{"class":412},"(flat.columns.tolist())\n",[377,1191,1192],{"class":318,"line":470},[377,1193,1194],{"class":811},"# ['region', 'quarter', 'revenue_A', 'revenue_B', 'units_A', 'units_B']\n",[377,1196,1197],{"class":318,"line":496},[377,1198,426],{"emptyLinePlaceholder":425},[377,1200,1201,1204,1207,1209,1211,1213,1215,1217,1219,1221,1223],{"class":318,"line":522},[377,1202,1203],{"class":412},"flat.to_excel(",[377,1205,1206],{"class":386},"\"pivot_flat.xlsx\"",[377,1208,453],{"class":412},[377,1210,705],{"class":591},[377,1212,435],{"class":408},[377,1214,710],{"class":386},[377,1216,453],{"class":412},[377,1218,592],{"class":591},[377,1220,435],{"class":408},[377,1222,861],{"class":530},[377,1224,650],{"class":412},[10,1226,1227,1228,1232],{},"The result has one header row, no merged cells and no index-name row, so it can become an Excel table with filter buttons straight away — see ",[27,1229,1231],{"href":1230},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fcreating-excel-tables-and-autofilters-with-python\u002Fcreate-an-excel-table-with-python\u002F","Create an Excel Table with Python",". Flattening also avoids the error in the next step.",[363,1234,1236],{"id":1235},"step-4-the-indexfalse-error-and-what-to-do-about-it","Step 4 — The index=False error, and what to do about it",[10,1238,1239],{},"Dropping the index is the usual way to tidy a plain export, so people try it here too:",[368,1241,1243],{"className":399,"code":1242,"language":401,"meta":373,"style":373},"try:\n    pivot.to_excel(\"pivot_noindex.xlsx\", index=False)\nexcept NotImplementedError as exc:\n    print(exc)\n# Writing to Excel with MultiIndex columns and no index ('index'=False) is not yet implemented.\n",[14,1244,1245,1253,1271,1285,1292],{"__ignoreMap":373},[377,1246,1247,1250],{"class":318,"line":379},[377,1248,1249],{"class":408},"try",[377,1251,1252],{"class":412},":\n",[377,1254,1255,1258,1261,1263,1265,1267,1269],{"class":318,"line":422},[377,1256,1257],{"class":412},"    pivot.to_excel(",[377,1259,1260],{"class":386},"\"pivot_noindex.xlsx\"",[377,1262,453],{"class":412},[377,1264,592],{"class":591},[377,1266,435],{"class":408},[377,1268,861],{"class":530},[377,1270,650],{"class":412},[377,1272,1273,1276,1279,1282],{"class":318,"line":429},[377,1274,1275],{"class":408},"except",[377,1277,1278],{"class":530}," NotImplementedError",[377,1280,1281],{"class":408}," as",[377,1283,1284],{"class":412}," exc:\n",[377,1286,1287,1289],{"class":318,"line":441},[377,1288,775],{"class":530},[377,1290,1291],{"class":412},"(exc)\n",[377,1293,1294],{"class":318,"line":470},[377,1295,1296],{"class":811},"# Writing to Excel with MultiIndex columns and no index ('index'=False) is not yet implemented.\n",[10,1298,1299,1300,1302,1303,1305,1306,1308,1309,1313,1314,1316],{},"pandas needs the index columns to anchor the multi-row column header, so it refuses. The fix depends on what you wanted from ",[14,1301,24],{},". If you wanted the row labels as normal columns, call ",[14,1304,1101],{}," first and keep ",[14,1307,24],{},". If you wanted no row labels at all, flatten the columns as in Step 3. A MultiIndex on the ",[1310,1311,1312],"em",{},"rows"," only, with ordinary columns, writes fine with ",[14,1315,24],{}," — the error is specific to hierarchical columns.",[363,1318,1320],{"id":1319},"step-5-read-the-hierarchy-back","Step 5 — Read the hierarchy back",[10,1322,1323,1324,1327],{},"A sheet written with the default layout round-trips exactly, as long as you tell ",[14,1325,1326],{},"read_excel"," the shape: how many header rows, and how many leading columns form the index.",[368,1329,1331],{"className":399,"code":1330,"language":401,"meta":373,"style":373},"back = pd.read_excel(\"pivot_default.xlsx\", sheet_name=\"Pivot\", header=[0, 1], index_col=[0, 1])\nprint(back.columns.tolist()[:2])     # [('revenue', 'A'), ('revenue', 'B')]\nprint(back.index.names)              # ['region', 'quarter']\nprint(back.equals(pivot))            # True\n",[14,1332,1333,1386,1401,1411],{"__ignoreMap":373},[377,1334,1335,1338,1340,1343,1345,1347,1349,1351,1353,1355,1358,1360,1362,1364,1366,1368,1370,1373,1375,1377,1379,1381,1383],{"class":318,"line":379},[377,1336,1337],{"class":412},"back ",[377,1339,435],{"class":408},[377,1341,1342],{"class":412}," pd.read_excel(",[377,1344,700],{"class":386},[377,1346,453],{"class":412},[377,1348,705],{"class":591},[377,1350,435],{"class":408},[377,1352,710],{"class":386},[377,1354,453],{"class":412},[377,1356,1357],{"class":591},"header",[377,1359,435],{"class":408},[377,1361,597],{"class":412},[377,1363,55],{"class":530},[377,1365,453],{"class":412},[377,1367,124],{"class":530},[377,1369,608],{"class":412},[377,1371,1372],{"class":591},"index_col",[377,1374,435],{"class":408},[377,1376,597],{"class":412},[377,1378,55],{"class":530},[377,1380,453],{"class":412},[377,1382,124],{"class":530},[377,1384,1385],{"class":412},"])\n",[377,1387,1388,1390,1393,1395,1398],{"class":318,"line":422},[377,1389,656],{"class":530},[377,1391,1392],{"class":412},"(back.columns.tolist()[:",[377,1394,150],{"class":530},[377,1396,1397],{"class":412},"])     ",[377,1399,1400],{"class":811},"# [('revenue', 'A'), ('revenue', 'B')]\n",[377,1402,1403,1405,1408],{"class":318,"line":429},[377,1404,656],{"class":530},[377,1406,1407],{"class":412},"(back.index.names)              ",[377,1409,1410],{"class":811},"# ['region', 'quarter']\n",[377,1412,1413,1415,1418],{"class":318,"line":441},[377,1414,656],{"class":530},[377,1416,1417],{"class":412},"(back.equals(pivot))            ",[377,1419,1420],{"class":811},"# True\n",[10,1422,1423,1424,1427,1428,1430,1431,1434],{},"pandas recognises the index-name row, fills merged labels down and across, and rebuilds both MultiIndexes with their names. The flattened file, by contrast, reads back as an ordinary frame; restore the hierarchy with ",[14,1425,1426],{},"set_index([\"region\", \"quarter\"])"," and by splitting the joined names — which is why a separator that cannot appear in the labels, such as ",[14,1429,1154],{}," or ",[14,1432,1433],{},"|",", is worth choosing deliberately.",[33,1436,42,1441,42,1444,42,1447,42,1450,42,1453,42,1459,42,1465,42,1469,42,1473,42,1478,42,1481,42,1484,42,1491,42,1495,42,1498,42,1501,42,1505,42,1508,42,1511,42,1517,42,1520,42,1523,42,1526,42,1530,42,1533,42,1536,42,1541,42,1546,42,1549],{"viewBox":1437,"role":36,"ariaLabelledBy":1438,"xmlns":40,"style":938},"4 0 752 142",[1439,1440],"mi-roundtrip-t","mi-roundtrip-d",[44,1442,1443],{"id":1439},"Round-tripping a MultiIndex through Excel",[48,1445,1446],{"id":1440},"A pivot with MultiIndex rows and columns is written with to_excel, producing two header rows, an index-name row and merged labels. read_excel with header set to rows 0 and 1 and index_col set to columns 0 and 1 rebuilds the same frame, and equals returns True.",[52,1448],{"x":208,"y":55,"width":947,"height":1449,"fill":58},"142",[52,1451],{"x":560,"y":62,"width":1452,"height":334,"rx":335,"fill":132,"stroke":133,"style":323},"153.0",[337,1454,1456],{"transform":1455},"translate(84.5,29.0) scale(1.000)",[341,1457],{"d":1458,"fill":344,"stroke":960,"style":346},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14",[73,1460,1464],{"x":1461,"y":1462,"style":1463},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","pivot",[73,1466,1468],{"x":1461,"y":1467,"style":356},"95.0","2 row levels, 2 column",[73,1470,1472],{"x":1461,"y":1471,"style":356},"112.5","levels",[318,1474],{"x1":1475,"y1":1476,"x2":1477,"y2":1476,"stroke":133,"style":323},"178.0","71.2","200.0",[325,1479],{"points":1480,"fill":328},"204.0,71.2 195.0,76.2 195.0,66.2",[52,1482],{"x":1483,"y":62,"width":1452,"height":334,"rx":335,"fill":65,"stroke":66,"style":323},"209.0",[337,1485,1487],{"transform":1486},"translate(273.5,29.0) scale(1.000)",[341,1488],{"d":1489,"fill":344,"stroke":1490,"style":346},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","var(--text,#172033)",[73,1492,669],{"x":1493,"y":1462,"style":1494},"285.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[73,1496,1497],{"x":1493,"y":1467,"style":356},"merged labels + name",[73,1499,1500],{"x":1493,"y":1471,"style":356},"row",[318,1502],{"x1":1503,"y1":1476,"x2":1504,"y2":1476,"stroke":133,"style":323},"367.0","389.0",[325,1506],{"points":1507,"fill":328},"393.0,71.2 384.0,76.2 384.0,66.2",[52,1509],{"x":1510,"y":62,"width":1452,"height":334,"rx":335,"fill":132,"stroke":133,"style":323},"398.0",[337,1512,1514],{"transform":1513},"translate(462.5,29.0) scale(1.000)",[341,1515],{"d":1516,"fill":344,"stroke":960,"style":346},"M9 8l-4 4 4 4 M15 8l4 4-4 4",[73,1518,1326],{"x":1519,"y":1462,"style":1463},"474.5",[73,1521,1522],{"x":1519,"y":1467,"style":356},"header=[0, 1],",[73,1524,1525],{"x":1519,"y":1471,"style":356},"index_col=[0, 1]",[318,1527],{"x1":1528,"y1":1476,"x2":1529,"y2":1476,"stroke":133,"style":323},"556.0","578.0",[325,1531],{"points":1532,"fill":328},"582.0,71.2 573.0,76.2 573.0,66.2",[52,1534],{"x":1535,"y":62,"width":1452,"height":334,"rx":335,"fill":212,"stroke":213,"style":323},"587.0",[337,1537,1539],{"transform":1538},"translate(651.5,29.0) scale(1.000)",[341,1540],{"d":984,"fill":344,"stroke":1057,"style":346},[73,1542,1545],{"x":1543,"y":1462,"style":1544},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","equals → True",[73,1547,1548],{"x":1543,"y":1467,"style":356},"names and levels",[73,1550,1551],{"x":1543,"y":1471,"style":356},"restored",[363,1553,1555],{"id":1554},"step-6-present-a-flattened-pivot-so-it-still-reads-as-a-hierarchy","Step 6 — Present a flattened pivot so it still reads as a hierarchy",[10,1557,1558],{},"Flattening trades the visual hierarchy for usability, but you can win most of it back with light formatting: readable column names, a frozen header, number formats per measure, and a blank-looking repeat of the outer key. The last trick keeps the region on every row for filtering while showing it only on the first row of each group, using a conditional format that fades repeated values rather than deleting them:",[368,1560,1562],{"className":399,"code":1561,"language":401,"meta":373,"style":373},"from openpyxl.formatting.rule import FormulaRule\nfrom openpyxl.styles import Font\n\nreadable = pivot.copy()\nreadable.columns = [f\"{measure.title()} ({product})\" for measure, product in readable.columns]\nreadable = readable.reset_index().fillna(0)\n\nwith pd.ExcelWriter(\"pivot_report.xlsx\", engine=\"openpyxl\") as writer:\n    readable.to_excel(writer, sheet_name=\"Pivot\", index=False)\n    ws = writer.sheets[\"Pivot\"]\n    ws.freeze_panes = \"C2\"\n    for cell in ws[1]:\n        cell.font = Font(bold=True)\n    for col in (\"C\", \"D\"):\n        for cell in ws[col][1:]:\n            cell.number_format = \"#,##0.00\"\n    last = ws.max_row\n    ws.conditional_formatting.add(\n        f\"A3:A{last}\",\n        FormulaRule(formula=[\"A3=A2\"], font=Font(color=\"FFFFFF\")),\n    )\n\nprint(pd.read_excel(\"pivot_report.xlsx\").columns.tolist()[:4])\n# ['region', 'quarter', 'Revenue (A)', 'Revenue (B)']\n",[14,1563,1564,1576,1588,1592,1601,1642,1655,1659,1688,1709,1723,1733,1751,1771,1793,1811,1822,1833,1839,1859,1896,1902,1907,1924],{"__ignoreMap":373},[377,1565,1566,1568,1571,1573],{"class":318,"line":379},[377,1567,680],{"class":408},[377,1569,1570],{"class":412}," openpyxl.formatting.rule ",[377,1572,409],{"class":408},[377,1574,1575],{"class":412}," FormulaRule\n",[377,1577,1578,1580,1583,1585],{"class":318,"line":422},[377,1579,680],{"class":408},[377,1581,1582],{"class":412}," openpyxl.styles ",[377,1584,409],{"class":408},[377,1586,1587],{"class":412}," Font\n",[377,1589,1590],{"class":318,"line":429},[377,1591,426],{"emptyLinePlaceholder":425},[377,1593,1594,1597,1599],{"class":318,"line":441},[377,1595,1596],{"class":412},"readable ",[377,1598,435],{"class":408},[377,1600,1125],{"class":412},[377,1602,1603,1606,1608,1610,1612,1614,1616,1619,1621,1624,1626,1628,1630,1633,1635,1637,1639],{"class":318,"line":470},[377,1604,1605],{"class":412},"readable.columns ",[377,1607,435],{"class":408},[377,1609,1135],{"class":412},[377,1611,1138],{"class":408},[377,1613,1141],{"class":386},[377,1615,1145],{"class":1144},[377,1617,1618],{"class":412},"measure.title()",[377,1620,1151],{"class":1144},[377,1622,1623],{"class":386}," (",[377,1625,1145],{"class":1144},[377,1627,157],{"class":412},[377,1629,1151],{"class":1144},[377,1631,1632],{"class":386},")\"",[377,1634,1165],{"class":408},[377,1636,1168],{"class":412},[377,1638,747],{"class":408},[377,1640,1641],{"class":412}," readable.columns]\n",[377,1643,1644,1646,1648,1651,1653],{"class":318,"line":496},[377,1645,1596],{"class":412},[377,1647,435],{"class":408},[377,1649,1650],{"class":412}," readable.reset_index().fillna(",[377,1652,55],{"class":530},[377,1654,650],{"class":412},[377,1656,1657],{"class":318,"line":522},[377,1658,426],{"emptyLinePlaceholder":425},[377,1660,1661,1664,1667,1670,1672,1675,1677,1680,1683,1685],{"class":318,"line":547},[377,1662,1663],{"class":408},"with",[377,1665,1666],{"class":412}," pd.ExcelWriter(",[377,1668,1669],{"class":386},"\"pivot_report.xlsx\"",[377,1671,453],{"class":412},[377,1673,1674],{"class":591},"engine",[377,1676,435],{"class":408},[377,1678,1679],{"class":386},"\"openpyxl\"",[377,1681,1682],{"class":412},") ",[377,1684,416],{"class":408},[377,1686,1687],{"class":412}," writer:\n",[377,1689,1690,1693,1695,1697,1699,1701,1703,1705,1707],{"class":318,"line":574},[377,1691,1692],{"class":412},"    readable.to_excel(writer, ",[377,1694,705],{"class":591},[377,1696,435],{"class":408},[377,1698,710],{"class":386},[377,1700,453],{"class":412},[377,1702,592],{"class":591},[377,1704,435],{"class":408},[377,1706,861],{"class":530},[377,1708,650],{"class":412},[377,1710,1711,1714,1716,1719,1721],{"class":318,"line":580},[377,1712,1713],{"class":412},"    ws ",[377,1715,435],{"class":408},[377,1717,1718],{"class":412}," writer.sheets[",[377,1720,710],{"class":386},[377,1722,736],{"class":412},[377,1724,1725,1728,1730],{"class":318,"line":622},[377,1726,1727],{"class":412},"    ws.freeze_panes ",[377,1729,435],{"class":408},[377,1731,1732],{"class":386}," \"C2\"\n",[377,1734,1735,1738,1741,1743,1746,1748],{"class":318,"line":653},[377,1736,1737],{"class":408},"    for",[377,1739,1740],{"class":412}," cell ",[377,1742,747],{"class":408},[377,1744,1745],{"class":412}," ws[",[377,1747,124],{"class":530},[377,1749,1750],{"class":412},"]:\n",[377,1752,1754,1757,1759,1762,1765,1767,1769],{"class":318,"line":1753},13,[377,1755,1756],{"class":412},"        cell.font ",[377,1758,435],{"class":408},[377,1760,1761],{"class":412}," Font(",[377,1763,1764],{"class":591},"bold",[377,1766,435],{"class":408},[377,1768,767],{"class":530},[377,1770,650],{"class":412},[377,1772,1774,1776,1779,1781,1783,1786,1788,1791],{"class":318,"line":1773},14,[377,1775,1737],{"class":408},[377,1777,1778],{"class":412}," col ",[377,1780,747],{"class":408},[377,1782,1623],{"class":412},[377,1784,1785],{"class":386},"\"C\"",[377,1787,453],{"class":412},[377,1789,1790],{"class":386},"\"D\"",[377,1792,770],{"class":412},[377,1794,1796,1799,1801,1803,1806,1808],{"class":318,"line":1795},15,[377,1797,1798],{"class":408},"        for",[377,1800,1740],{"class":412},[377,1802,747],{"class":408},[377,1804,1805],{"class":412}," ws[col][",[377,1807,124],{"class":530},[377,1809,1810],{"class":412},":]:\n",[377,1812,1814,1817,1819],{"class":318,"line":1813},16,[377,1815,1816],{"class":412},"            cell.number_format ",[377,1818,435],{"class":408},[377,1820,1821],{"class":386}," \"#,##0.00\"\n",[377,1823,1825,1828,1830],{"class":318,"line":1824},17,[377,1826,1827],{"class":412},"    last ",[377,1829,435],{"class":408},[377,1831,1832],{"class":412}," ws.max_row\n",[377,1834,1836],{"class":318,"line":1835},18,[377,1837,1838],{"class":412},"    ws.conditional_formatting.add(\n",[377,1840,1842,1845,1848,1850,1853,1855,1857],{"class":318,"line":1841},19,[377,1843,1844],{"class":408},"        f",[377,1846,1847],{"class":386},"\"A3:A",[377,1849,1145],{"class":1144},[377,1851,1852],{"class":412},"last",[377,1854,1151],{"class":1144},[377,1856,1141],{"class":386},[377,1858,619],{"class":412},[377,1860,1862,1865,1868,1870,1872,1875,1877,1880,1882,1885,1888,1890,1893],{"class":318,"line":1861},20,[377,1863,1864],{"class":412},"        FormulaRule(",[377,1866,1867],{"class":591},"formula",[377,1869,435],{"class":408},[377,1871,597],{"class":412},[377,1873,1874],{"class":386},"\"A3=A2\"",[377,1876,608],{"class":412},[377,1878,1879],{"class":591},"font",[377,1881,435],{"class":408},[377,1883,1884],{"class":412},"Font(",[377,1886,1887],{"class":591},"color",[377,1889,435],{"class":408},[377,1891,1892],{"class":386},"\"FFFFFF\"",[377,1894,1895],{"class":412},")),\n",[377,1897,1899],{"class":318,"line":1898},21,[377,1900,1901],{"class":412},"    )\n",[377,1903,1905],{"class":318,"line":1904},22,[377,1906,426],{"emptyLinePlaceholder":425},[377,1908,1910,1912,1915,1917,1920,1922],{"class":318,"line":1909},23,[377,1911,656],{"class":530},[377,1913,1914],{"class":412},"(pd.read_excel(",[377,1916,1669],{"class":386},[377,1918,1919],{"class":412},").columns.tolist()[:",[377,1921,208],{"class":530},[377,1923,1385],{"class":412},[377,1925,1927],{"class":318,"line":1926},24,[377,1928,1929],{"class":811},"# ['region', 'quarter', 'Revenue (A)', 'Revenue (B)']\n",[10,1931,1932,1933,1936],{},"The repeated region names are still in the cells — filters, lookups and copy-paste all see them — but the rule paints them white when they equal the row above, so the sheet reads like a grouped report. If someone sorts it, the rule re-evaluates and the first row of each new group shows its label again. Freezing at ",[14,1934,1935],{},"C2"," keeps both the header and the two key columns visible while scrolling across a wide pivot.",[363,1938,1940],{"id":1939},"common-pitfalls","Common pitfalls",[1942,1943,1944,1960,1974,1980],"ul",{},[1945,1946,1947,1951,1952,1955,1956,1959],"li",{},[1948,1949,1950],"strong",{},"Missing values in the pivot."," Empty combinations appear as blank cells. If the reader needs zeros, pass ",[14,1953,1954],{},"fill_value=0"," to ",[14,1957,1958],{},"pivot_table"," rather than filling after the fact.",[1945,1961,1962,1965,1966,1969,1970,1973],{},[1948,1963,1964],{},"Header rows miscounted on read."," Two column levels means ",[14,1967,1968],{},"header=[0, 1]","; three means ",[14,1971,1972],{},"[0, 1, 2]",". Get it wrong and the index-name row is read as data.",[1945,1975,1976,1979],{},[1948,1977,1978],{},"Styling merged header cells."," Formatting the merged header after writing requires styling the top-left cell of each merged range; the other cells in the range are hidden. When you need designed headers, flatten first and style one row.",[1945,1981,1982,1985],{},[1948,1983,1984],{},"Very wide pivots."," A column level with hundreds of values produces hundreds of columns that no one reads. Pivot the long dimension onto the rows instead.",[363,1987,1989],{"id":1988},"performance-and-scale-notes","Performance and scale notes",[10,1991,1992,1993,1997],{},"MultiIndex frames write at the same speed as flat ones, but merged cells cost openpyxl and Excel time on very large sheets, and they make every later edit slower. For summary tables — the usual home of a MultiIndex — none of this matters. For detail data, never write a MultiIndex at all: reset the index and let Excel's own pivot table build the hierarchy on demand, as described in ",[27,1994,1996],{"href":1995},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fadd-a-native-excel-pivot-table-with-python\u002F","Add a Native Excel Pivot Table with Python",".",[363,1999,2001],{"id":2000},"conclusion","Conclusion",[10,2003,2004,2005,2007,2008,2010,2011,2013,2014,818,2016,2018],{},"pandas writes MultiIndex rows and columns faithfully: multiple header rows, merged outer labels and a row of index names. Keep that default for read-only reports, use ",[14,2006,830],{}," when people will sort, and flatten with joined column names plus ",[14,2009,1101],{}," when the sheet needs filters, tables or further work. Never combine hierarchical columns with ",[14,2012,24],{},", and read the default layout back with matching ",[14,2015,1357],{},[14,2017,1372],{}," lists to recover the exact frame.",[363,2020,2022],{"id":2021},"frequently-asked-questions","Frequently asked questions",[10,2024,2025,2028],{},[1948,2026,2027],{},"Why does to_excel raise NotImplementedError for my MultiIndex?","\npandas cannot write MultiIndex columns without also writing the index, so index=False with hierarchical columns raises NotImplementedError. Either keep the index or flatten the columns into single strings first.",[10,2030,2031,2034],{},[1948,2032,2033],{},"How do I stop pandas merging the index cells?","\nPass merge_cells=False. The outer labels are then repeated on every row and over every column they cover, which is what you want if people will sort or filter the sheet.",[10,2036,2037,2040],{},[1948,2038,2039],{},"Where does the empty row under the header come from?","\nWhen the index has names, pandas writes them on their own row between the column headers and the data. It keeps the index names but breaks Excel's filter and table features, which expect a single header row directly above the data.",[10,2042,2043,2046,2047,2050,2051,2053],{},[1948,2044,2045],{},"How do I read a MultiIndex sheet back into pandas?","\nTell read_excel how many header rows and index columns there are, for example header=",[377,2048,2049],{},"0, 1"," and index_col=",[377,2052,2049],{},". pandas then rebuilds both hierarchies, including the index names.",[363,2055,2057],{"id":2056},"related","Related",[1942,2059,2060,2066,2073,2080],{},[1945,2061,2062,2063,2065],{},"Up one level: ",[27,2064,30],{"href":29}," — the writer options in context.",[1945,2067,2068,2072],{},[27,2069,2071],{"href":2070},"\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)"," — styling the result.",[1945,2074,2075,2079],{},[27,2076,2078],{"href":2077},"\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"," — building the pivot in the first place.",[1945,2081,2082,2086],{},[27,2083,2085],{"href":2084},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-dataframe-to-a-specific-cell-in-excel\u002F","Write a DataFrame to a Specific Cell in Excel with pandas"," — placing the table inside a report layout.",[2088,2089,2090],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":373,"searchDepth":422,"depth":422,"links":2092},[2093,2094,2095,2096,2097,2098,2099,2100,2101,2102,2103,2104],{"id":365,"depth":422,"text":366},{"id":662,"depth":422,"text":663},{"id":824,"depth":422,"text":825},{"id":1094,"depth":422,"text":1095},{"id":1235,"depth":422,"text":1236},{"id":1319,"depth":422,"text":1320},{"id":1554,"depth":422,"text":1555},{"id":1939,"depth":422,"text":1940},{"id":1988,"depth":422,"text":1989},{"id":2000,"depth":422,"text":2001},{"id":2021,"depth":422,"text":2022},{"id":2056,"depth":422,"text":2057},"2026-09-23","Write pivot tables and grouped results with MultiIndex rows or columns to Excel — merged or repeated labels, the extra index-name row, the index=False error, flattening for filters, and reading it back.","md",[2109,2111,2113,2115],{"q":2027,"a":2110},"pandas cannot write MultiIndex columns without also writing the index, so index=False with hierarchical columns raises NotImplementedError. Either keep the index or flatten the columns into single strings first.",{"q":2033,"a":2112},"Pass merge_cells=False. The outer labels are then repeated on every row and over every column they cover, which is what you want if people will sort or filter the sheet.",{"q":2039,"a":2114},"When the index has names, pandas writes them on their own row between the column headers and the data. It keeps the index names but breaks Excel's filter and table features, which expect a single header row directly above the data.",{"q":2045,"a":2116},"Tell read_excel how many header rows and index columns there are, for example header=[0, 1] and index_col=[0, 1]. pandas then rebuilds both hierarchies, including the index names.","write a multiindex dataframe to excel with pandas",null,{"breadcrumb":2120},[2121,2124,2127],{"name":2122,"item":2123},"Home","\u002F",{"name":2125,"item":2126},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":30,"item":29},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel",{"title":2130,"description":2131},"Write a MultiIndex DataFrame to Excel (pandas to_excel)","How pandas writes MultiIndex rows and columns to Excel — merge_cells, the blank index-name row, NotImplementedError with index=False, flattening headers for filtering, and round-tripping with header and index_col.","write-a-multiindex-dataframe-to-excel","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-multiindex-dataframe-to-excel\u002Findex",[2135,2136,2137],"writing-excel","pivots-and-summaries","pandas","how-to","ERm5KGB9DKNF0kTJocNcTofk7coXt0IWWo2oYTepLXY",[2141,2145],{"title":2142,"path":2143,"stem":2144,"children":-1},"Write a List of Dictionaries to Excel Without pandas","\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-list-of-dictionaries-to-excel-without-pandas","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-a-list-of-dictionaries-to-excel-without-pandas\u002Findex",{"title":2146,"path":2147,"stem":2148,"children":-1},"Write Clickable Links from a DataFrame Column to Excel","\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-clickable-links-from-a-dataframe-to-excel","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-clickable-links-from-a-dataframe-to-excel\u002Findex",[2150,2152],{"path":2151,"title":2125},"\u002Fgetting-started-with-python-excel-automation",{"path":2153,"title":30},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas",[],[2156,2159,2162,2165,2168,2171],{"path":2157,"title":2158,"kind":2138},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file","Write Multiple DataFrames to One Excel File",{"path":2160,"title":2161,"kind":2138},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter","Write a Formatted Excel Report with xlsxwriter",{"path":2163,"title":2164,"kind":2138},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ffind-and-replace-text-in-excel-with-openpyxl","Find and Replace Text in Excel with openpyxl",{"path":2166,"title":2167,"kind":2138},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Finsert-and-delete-rows-and-columns-with-openpyxl","Insert and Delete Rows and Columns with openpyxl",{"path":2169,"title":2170,"kind":2138},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fopenpyxl-append-data-to-existing-excel-sheet","openpyxl: Append Data to an Existing Excel Sheet",{"path":2172,"title":2173,"kind":2138},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freorder-columns-to-match-a-template-layout-with-pandas","Reorder Columns to Match a Template Layout with pandas",1790972077800]