[{"data":1,"prerenderedAt":2893},["ShallowReactive",2],{"doc:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter":3,"surround:\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter":2886},{"id":4,"title":5,"body":6,"dateModified":2857,"datePublished":2857,"description":2858,"extension":2859,"faq":2860,"meta":2869,"navigation":215,"path":2879,"seo":2880,"slug":2882,"stem":2883,"type":2884,"__hash__":2885},"docs\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002Findex.md","Write a Formatted Excel Report with xlsxwriter",{"type":7,"value":8,"toc":2843},"minimark",[9,13,22,131,136,165,172,176,179,989,1003,1007,1010,1318,1331,1335,1338,1589,1613,1625,1629,1635,1894,1977,2001,2005,2008,2291,2297,2308,2436,2440,2443,2560,2569,2573,2710,2714,2721,2733,2737,2746,2750,2763,2772,2781,2790,2794,2797,2806,2809,2839],[10,11,12],"p",{},"A report generated by a script is judged in the two seconds before anyone reads a number: whether the header stands out, whether money looks like money, whether the columns are wide enough, whether the totals are visible. xlsxwriter is unusually good at all of that, provided you build the sheet in the order it wants — because a cell that has been written cannot be restyled afterwards.",[10,14,15,16,21],{},"This guide builds a full styled report top to bottom, using the format-object pattern from ",[17,18,20],"a",{"href":19},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002F","Building Excel Reports with xlsxwriter",". Everything below runs as written, generating its own sample data.",[23,24,32,33,32,37,32,41,32,48,32,56,32,63,32,68,32,72,32,76,32,79,32,84,32,89,32,92,32,96,32,101,32,104,32,108,32,111,32,116,32,123,32,128],"svg",{"viewBox":25,"role":26,"ariaLabelledBy":27,"xmlns":30,"style":31},"0 0 740 252","img",[28,29],"fw-anatomy-t","fw-anatomy-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:740px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[34,35,36],"title",{"id":28},"The anatomy of the finished sheet",[38,39,40],"desc",{"id":29},"Row one holds a merged title with the reporting period. Row two is a generated-at line in small grey type. Row four is the header, frozen so it stays visible, and carrying the autofilter. Rows five onward are the data, banded and formatted by column. The last row is a bold totals line built from SUBTOTAL formulas.",[42,43],"rect",{"x":44,"y":44,"width":45,"height":46,"fill":47},"0","740","252","#ffffff",[42,49],{"x":50,"y":51,"width":52,"height":53,"rx":54,"fill":55},"40","24","600","34","6","#5b5cf0",[57,58,62],"text",{"x":59,"y":60,"style":61},"60","46","font-size:12.5px;font-weight:700;fill:#ffffff","Regional sales — July 2026",[57,64,67],{"x":65,"y":60,"style":66},"668","font-size:11px;fill:var(--muted,#5b6780)","merged A1:E1",[42,69],{"x":50,"y":59,"width":52,"height":51,"rx":70,"fill":71},"4","#f0f2f5",[57,73,75],{"x":59,"y":74,"style":66},"77","Generated 2026-08-10 06:00 · 1,284 rows",[57,77,78],{"x":65,"y":74,"style":66},"row 2",[42,80],{"x":50,"y":81,"width":52,"height":82,"rx":70,"fill":83},"98","30","#1f4e78",[57,85,88],{"x":59,"y":86,"style":87},"118","font-size:11.5px;font-weight:700;fill:#ffffff","Region      Orders      Amount      Share      Updated",[57,90,91],{"x":65,"y":86,"style":66},"frozen + filter",[42,93],{"x":50,"y":94,"width":52,"height":51,"fill":95},"132","#f0f4ff",[57,97,100],{"x":59,"y":98,"style":99},"149","font-size:11px;fill:var(--text,#172033)","South         412        274.75       55.0%      2026-07-30",[42,102],{"x":50,"y":103,"width":52,"height":51,"fill":47},"158",[57,105,107],{"x":59,"y":106,"style":99},"175","North         388        150.25       30.0%      2026-07-28",[42,109],{"x":50,"y":110,"width":52,"height":51,"fill":95},"184",[57,112,115],{"x":59,"y":113,"style":114},"201","font-size:11px;fill:var(--text,#172033);","East          484         75.00       15.0%      2026-07-29",[42,117],{"x":50,"y":118,"width":52,"height":119,"rx":70,"fill":120,"stroke":121,"style":122},"210","28","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[57,124,127],{"x":59,"y":125,"style":126},"229","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157)","Total        1,284       500.00      100.0%",[57,129,130],{"x":65,"y":125,"style":66},"SUBTOTAL",[132,133,135],"h2",{"id":134},"prerequisites","Prerequisites",[137,138,143],"pre",{"className":139,"code":140,"language":141,"meta":142,"style":142},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install xlsxwriter pandas\n","bash","",[144,145,146],"code",{"__ignoreMap":142},[147,148,151,155,159,162],"span",{"class":149,"line":150},"line",1,[147,152,154],{"class":153},"sMTad","pip",[147,156,158],{"class":157},"srMev"," install",[147,160,161],{"class":157}," xlsxwriter",[147,163,164],{"class":157}," pandas\n",[10,166,167,168,171],{},"xlsxwriter has no dependencies and needs no Excel installation — it writes the ",[144,169,170],{},".xlsx"," format directly, so this runs on a headless server or a CI runner.",[132,173,175],{"id":174},"step-1-define-every-format-once","Step 1: Define every format once",[10,177,178],{},"Formats are created on the workbook and reused. Building them in a dictionary at the top keeps the writing code readable and guarantees each style exists exactly once in the file:",[137,180,184],{"className":181,"code":182,"language":183,"meta":142,"style":142},"language-python shiki shiki-themes github-light github-dark-high-contrast","import xlsxwriter\nimport pandas as pd\n\ndata = pd.DataFrame({\n    \"region\": [\"South\", \"North\", \"East\", \"West\"],\n    \"orders\": [412, 388, 484, 301],\n    \"amount\": [274.75, 150.25, 75.0, 190.4],\n    \"updated\": pd.to_datetime([\"2026-07-30\", \"2026-07-28\",\n                               \"2026-07-29\", \"2026-07-31\"]),\n})\ndata[\"share\"] = data[\"amount\"] \u002F data[\"amount\"].sum()\ndata = data[[\"region\", \"orders\", \"amount\", \"share\", \"updated\"]]\n\n\ndef make_formats(wb):\n    return {\n        \"title\":  wb.add_format({\"bold\": True, \"font_size\": 14,\n                                 \"font_color\": \"white\", \"bg_color\": \"#5B5CF0\",\n                                 \"align\": \"left\", \"valign\": \"vcenter\",\n                                 \"indent\": 1}),\n        \"note\":   wb.add_format({\"italic\": True, \"font_size\": 9,\n                                 \"font_color\": \"#5B6780\", \"indent\": 1}),\n        \"header\": wb.add_format({\"bold\": True, \"font_color\": \"white\",\n                                 \"bg_color\": \"#1F4E78\", \"align\": \"center\",\n                                 \"valign\": \"vcenter\", \"border\": 1,\n                                 \"text_wrap\": True}),\n        \"text\":   wb.add_format({\"border\": 1}),\n        \"int\":    wb.add_format({\"num_format\": \"#,##0\", \"border\": 1}),\n        \"money\":  wb.add_format({\"num_format\": '#,##0.00', \"border\": 1}),\n        \"pct\":    wb.add_format({\"num_format\": \"0.0%\", \"border\": 1}),\n        \"date\":   wb.add_format({\"num_format\": \"yyyy-mm-dd\", \"border\": 1}),\n        \"band\":   wb.add_format({\"bg_color\": \"#F0F4FF\", \"border\": 1}),\n        \"total_text\":  wb.add_format({\"bold\": True, \"top\": 2, \"bg_color\": \"#D9F4F1\"}),\n        \"total_int\":   wb.add_format({\"bold\": True, \"top\": 2, \"bg_color\": \"#D9F4F1\",\n                                      \"num_format\": \"#,##0\"}),\n        \"total_money\": wb.add_format({\"bold\": True, \"top\": 2, \"bg_color\": \"#D9F4F1\",\n                                      \"num_format\": '#,##0.00'}),\n        \"total_pct\":   wb.add_format({\"bold\": True, \"top\": 2, \"bg_color\": \"#D9F4F1\",\n                                      \"num_format\": \"0.0%\"}),\n    }\n","python",[144,185,186,196,210,217,229,260,289,317,337,351,357,389,423,428,433,446,455,485,508,531,545,572,593,619,642,663,675,691,718,743,768,793,818,853,885,897,929,940,972,983],{"__ignoreMap":142},[147,187,188,192],{"class":149,"line":150},[147,189,191],{"class":190},"s-kum","import",[147,193,195],{"class":194},"skGVy"," xlsxwriter\n",[147,197,199,201,204,207],{"class":149,"line":198},2,[147,200,191],{"class":190},[147,202,203],{"class":194}," pandas ",[147,205,206],{"class":190},"as",[147,208,209],{"class":194}," pd\n",[147,211,213],{"class":149,"line":212},3,[147,214,216],{"emptyLinePlaceholder":215},true,"\n",[147,218,220,223,226],{"class":149,"line":219},4,[147,221,222],{"class":194},"data ",[147,224,225],{"class":190},"=",[147,227,228],{"class":194}," pd.DataFrame({\n",[147,230,232,235,238,241,244,247,249,252,254,257],{"class":149,"line":231},5,[147,233,234],{"class":157},"    \"region\"",[147,236,237],{"class":194},": [",[147,239,240],{"class":157},"\"South\"",[147,242,243],{"class":194},", ",[147,245,246],{"class":157},"\"North\"",[147,248,243],{"class":194},[147,250,251],{"class":157},"\"East\"",[147,253,243],{"class":194},[147,255,256],{"class":157},"\"West\"",[147,258,259],{"class":194},"],\n",[147,261,263,266,268,272,274,277,279,282,284,287],{"class":149,"line":262},6,[147,264,265],{"class":157},"    \"orders\"",[147,267,237],{"class":194},[147,269,271],{"class":270},"sP0c6","412",[147,273,243],{"class":194},[147,275,276],{"class":270},"388",[147,278,243],{"class":194},[147,280,281],{"class":270},"484",[147,283,243],{"class":194},[147,285,286],{"class":270},"301",[147,288,259],{"class":194},[147,290,292,295,297,300,302,305,307,310,312,315],{"class":149,"line":291},7,[147,293,294],{"class":157},"    \"amount\"",[147,296,237],{"class":194},[147,298,299],{"class":270},"274.75",[147,301,243],{"class":194},[147,303,304],{"class":270},"150.25",[147,306,243],{"class":194},[147,308,309],{"class":270},"75.0",[147,311,243],{"class":194},[147,313,314],{"class":270},"190.4",[147,316,259],{"class":194},[147,318,320,323,326,329,331,334],{"class":149,"line":319},8,[147,321,322],{"class":157},"    \"updated\"",[147,324,325],{"class":194},": pd.to_datetime([",[147,327,328],{"class":157},"\"2026-07-30\"",[147,330,243],{"class":194},[147,332,333],{"class":157},"\"2026-07-28\"",[147,335,336],{"class":194},",\n",[147,338,340,343,345,348],{"class":149,"line":339},9,[147,341,342],{"class":157},"                               \"2026-07-29\"",[147,344,243],{"class":194},[147,346,347],{"class":157},"\"2026-07-31\"",[147,349,350],{"class":194},"]),\n",[147,352,354],{"class":149,"line":353},10,[147,355,356],{"class":194},"})\n",[147,358,360,363,366,369,371,374,377,379,382,384,386],{"class":149,"line":359},11,[147,361,362],{"class":194},"data[",[147,364,365],{"class":157},"\"share\"",[147,367,368],{"class":194},"] ",[147,370,225],{"class":190},[147,372,373],{"class":194}," data[",[147,375,376],{"class":157},"\"amount\"",[147,378,368],{"class":194},[147,380,381],{"class":190},"\u002F",[147,383,373],{"class":194},[147,385,376],{"class":157},[147,387,388],{"class":194},"].sum()\n",[147,390,392,394,396,399,402,404,407,409,411,413,415,417,420],{"class":149,"line":391},12,[147,393,222],{"class":194},[147,395,225],{"class":190},[147,397,398],{"class":194}," data[[",[147,400,401],{"class":157},"\"region\"",[147,403,243],{"class":194},[147,405,406],{"class":157},"\"orders\"",[147,408,243],{"class":194},[147,410,376],{"class":157},[147,412,243],{"class":194},[147,414,365],{"class":157},[147,416,243],{"class":194},[147,418,419],{"class":157},"\"updated\"",[147,421,422],{"class":194},"]]\n",[147,424,426],{"class":149,"line":425},13,[147,427,216],{"emptyLinePlaceholder":215},[147,429,431],{"class":149,"line":430},14,[147,432,216],{"emptyLinePlaceholder":215},[147,434,436,439,443],{"class":149,"line":435},15,[147,437,438],{"class":190},"def",[147,440,442],{"class":441},"s_Opv"," make_formats",[147,444,445],{"class":194},"(wb):\n",[147,447,449,452],{"class":149,"line":448},16,[147,450,451],{"class":190},"    return",[147,453,454],{"class":194}," {\n",[147,456,458,461,464,467,470,473,475,478,480,483],{"class":149,"line":457},17,[147,459,460],{"class":157},"        \"title\"",[147,462,463],{"class":194},":  wb.add_format({",[147,465,466],{"class":157},"\"bold\"",[147,468,469],{"class":194},": ",[147,471,472],{"class":270},"True",[147,474,243],{"class":194},[147,476,477],{"class":157},"\"font_size\"",[147,479,469],{"class":194},[147,481,482],{"class":270},"14",[147,484,336],{"class":194},[147,486,488,491,493,496,498,501,503,506],{"class":149,"line":487},18,[147,489,490],{"class":157},"                                 \"font_color\"",[147,492,469],{"class":194},[147,494,495],{"class":157},"\"white\"",[147,497,243],{"class":194},[147,499,500],{"class":157},"\"bg_color\"",[147,502,469],{"class":194},[147,504,505],{"class":157},"\"#5B5CF0\"",[147,507,336],{"class":194},[147,509,511,514,516,519,521,524,526,529],{"class":149,"line":510},19,[147,512,513],{"class":157},"                                 \"align\"",[147,515,469],{"class":194},[147,517,518],{"class":157},"\"left\"",[147,520,243],{"class":194},[147,522,523],{"class":157},"\"valign\"",[147,525,469],{"class":194},[147,527,528],{"class":157},"\"vcenter\"",[147,530,336],{"class":194},[147,532,534,537,539,542],{"class":149,"line":533},20,[147,535,536],{"class":157},"                                 \"indent\"",[147,538,469],{"class":194},[147,540,541],{"class":270},"1",[147,543,544],{"class":194},"}),\n",[147,546,548,551,554,557,559,561,563,565,567,570],{"class":149,"line":547},21,[147,549,550],{"class":157},"        \"note\"",[147,552,553],{"class":194},":   wb.add_format({",[147,555,556],{"class":157},"\"italic\"",[147,558,469],{"class":194},[147,560,472],{"class":270},[147,562,243],{"class":194},[147,564,477],{"class":157},[147,566,469],{"class":194},[147,568,569],{"class":270},"9",[147,571,336],{"class":194},[147,573,575,577,579,582,584,587,589,591],{"class":149,"line":574},22,[147,576,490],{"class":157},[147,578,469],{"class":194},[147,580,581],{"class":157},"\"#5B6780\"",[147,583,243],{"class":194},[147,585,586],{"class":157},"\"indent\"",[147,588,469],{"class":194},[147,590,541],{"class":270},[147,592,544],{"class":194},[147,594,596,599,602,604,606,608,610,613,615,617],{"class":149,"line":595},23,[147,597,598],{"class":157},"        \"header\"",[147,600,601],{"class":194},": wb.add_format({",[147,603,466],{"class":157},[147,605,469],{"class":194},[147,607,472],{"class":270},[147,609,243],{"class":194},[147,611,612],{"class":157},"\"font_color\"",[147,614,469],{"class":194},[147,616,495],{"class":157},[147,618,336],{"class":194},[147,620,622,625,627,630,632,635,637,640],{"class":149,"line":621},24,[147,623,624],{"class":157},"                                 \"bg_color\"",[147,626,469],{"class":194},[147,628,629],{"class":157},"\"#1F4E78\"",[147,631,243],{"class":194},[147,633,634],{"class":157},"\"align\"",[147,636,469],{"class":194},[147,638,639],{"class":157},"\"center\"",[147,641,336],{"class":194},[147,643,645,648,650,652,654,657,659,661],{"class":149,"line":644},25,[147,646,647],{"class":157},"                                 \"valign\"",[147,649,469],{"class":194},[147,651,528],{"class":157},[147,653,243],{"class":194},[147,655,656],{"class":157},"\"border\"",[147,658,469],{"class":194},[147,660,541],{"class":270},[147,662,336],{"class":194},[147,664,666,669,671,673],{"class":149,"line":665},26,[147,667,668],{"class":157},"                                 \"text_wrap\"",[147,670,469],{"class":194},[147,672,472],{"class":270},[147,674,544],{"class":194},[147,676,678,681,683,685,687,689],{"class":149,"line":677},27,[147,679,680],{"class":157},"        \"text\"",[147,682,553],{"class":194},[147,684,656],{"class":157},[147,686,469],{"class":194},[147,688,541],{"class":270},[147,690,544],{"class":194},[147,692,694,697,700,703,705,708,710,712,714,716],{"class":149,"line":693},28,[147,695,696],{"class":157},"        \"int\"",[147,698,699],{"class":194},":    wb.add_format({",[147,701,702],{"class":157},"\"num_format\"",[147,704,469],{"class":194},[147,706,707],{"class":157},"\"#,##0\"",[147,709,243],{"class":194},[147,711,656],{"class":157},[147,713,469],{"class":194},[147,715,541],{"class":270},[147,717,544],{"class":194},[147,719,721,724,726,728,730,733,735,737,739,741],{"class":149,"line":720},29,[147,722,723],{"class":157},"        \"money\"",[147,725,463],{"class":194},[147,727,702],{"class":157},[147,729,469],{"class":194},[147,731,732],{"class":157},"'#,##0.00'",[147,734,243],{"class":194},[147,736,656],{"class":157},[147,738,469],{"class":194},[147,740,541],{"class":270},[147,742,544],{"class":194},[147,744,746,749,751,753,755,758,760,762,764,766],{"class":149,"line":745},30,[147,747,748],{"class":157},"        \"pct\"",[147,750,699],{"class":194},[147,752,702],{"class":157},[147,754,469],{"class":194},[147,756,757],{"class":157},"\"0.0%\"",[147,759,243],{"class":194},[147,761,656],{"class":157},[147,763,469],{"class":194},[147,765,541],{"class":270},[147,767,544],{"class":194},[147,769,771,774,776,778,780,783,785,787,789,791],{"class":149,"line":770},31,[147,772,773],{"class":157},"        \"date\"",[147,775,553],{"class":194},[147,777,702],{"class":157},[147,779,469],{"class":194},[147,781,782],{"class":157},"\"yyyy-mm-dd\"",[147,784,243],{"class":194},[147,786,656],{"class":157},[147,788,469],{"class":194},[147,790,541],{"class":270},[147,792,544],{"class":194},[147,794,796,799,801,803,805,808,810,812,814,816],{"class":149,"line":795},32,[147,797,798],{"class":157},"        \"band\"",[147,800,553],{"class":194},[147,802,500],{"class":157},[147,804,469],{"class":194},[147,806,807],{"class":157},"\"#F0F4FF\"",[147,809,243],{"class":194},[147,811,656],{"class":157},[147,813,469],{"class":194},[147,815,541],{"class":270},[147,817,544],{"class":194},[147,819,821,824,826,828,830,832,834,837,839,842,844,846,848,851],{"class":149,"line":820},33,[147,822,823],{"class":157},"        \"total_text\"",[147,825,463],{"class":194},[147,827,466],{"class":157},[147,829,469],{"class":194},[147,831,472],{"class":270},[147,833,243],{"class":194},[147,835,836],{"class":157},"\"top\"",[147,838,469],{"class":194},[147,840,841],{"class":270},"2",[147,843,243],{"class":194},[147,845,500],{"class":157},[147,847,469],{"class":194},[147,849,850],{"class":157},"\"#D9F4F1\"",[147,852,544],{"class":194},[147,854,856,859,861,863,865,867,869,871,873,875,877,879,881,883],{"class":149,"line":855},34,[147,857,858],{"class":157},"        \"total_int\"",[147,860,553],{"class":194},[147,862,466],{"class":157},[147,864,469],{"class":194},[147,866,472],{"class":270},[147,868,243],{"class":194},[147,870,836],{"class":157},[147,872,469],{"class":194},[147,874,841],{"class":270},[147,876,243],{"class":194},[147,878,500],{"class":157},[147,880,469],{"class":194},[147,882,850],{"class":157},[147,884,336],{"class":194},[147,886,888,891,893,895],{"class":149,"line":887},35,[147,889,890],{"class":157},"                                      \"num_format\"",[147,892,469],{"class":194},[147,894,707],{"class":157},[147,896,544],{"class":194},[147,898,900,903,905,907,909,911,913,915,917,919,921,923,925,927],{"class":149,"line":899},36,[147,901,902],{"class":157},"        \"total_money\"",[147,904,601],{"class":194},[147,906,466],{"class":157},[147,908,469],{"class":194},[147,910,472],{"class":270},[147,912,243],{"class":194},[147,914,836],{"class":157},[147,916,469],{"class":194},[147,918,841],{"class":270},[147,920,243],{"class":194},[147,922,500],{"class":157},[147,924,469],{"class":194},[147,926,850],{"class":157},[147,928,336],{"class":194},[147,930,932,934,936,938],{"class":149,"line":931},37,[147,933,890],{"class":157},[147,935,469],{"class":194},[147,937,732],{"class":157},[147,939,544],{"class":194},[147,941,943,946,948,950,952,954,956,958,960,962,964,966,968,970],{"class":149,"line":942},38,[147,944,945],{"class":157},"        \"total_pct\"",[147,947,553],{"class":194},[147,949,466],{"class":157},[147,951,469],{"class":194},[147,953,472],{"class":270},[147,955,243],{"class":194},[147,957,836],{"class":157},[147,959,469],{"class":194},[147,961,841],{"class":270},[147,963,243],{"class":194},[147,965,500],{"class":157},[147,967,469],{"class":194},[147,969,850],{"class":157},[147,971,336],{"class":194},[147,973,975,977,979,981],{"class":149,"line":974},39,[147,976,890],{"class":157},[147,978,469],{"class":194},[147,980,757],{"class":157},[147,982,544],{"class":194},[147,984,986],{"class":149,"line":985},40,[147,987,988],{"class":194},"    }\n",[10,990,991,992,243,995,998,999,1002],{},"The duplication in the total formats is unavoidable and worth accepting: a format object carries every attribute at once, so \"bold, top border, shaded, and a currency code\" is one object rather than four composable ones. This is the main structural difference from openpyxl's separate ",[144,993,994],{},"Font",[144,996,997],{},"Fill"," and ",[144,1000,1001],{},"Border"," objects.",[132,1004,1006],{"id":1005},"step-2-write-the-title-block-and-the-header","Step 2: Write the title block and the header",[10,1008,1009],{},"The title is merged across the table's width, and the generated-at line goes directly beneath it:",[137,1011,1013],{"className":181,"code":1012,"language":183,"meta":142,"style":142},"HEADERS = [\"Region\", \"Orders\", \"Amount\", \"Share\", \"Updated\"]\nFIRST_DATA_ROW = 3                        # zero-based: title, note, blank, header at 3\n\nwb = xlsxwriter.Workbook(\"regional-july.xlsx\")\nfmt = make_formats(wb)\nws = wb.add_worksheet(\"Summary\")\n\nws.merge_range(0, 0, 0, len(HEADERS) - 1, \"Regional sales — July 2026\", fmt[\"title\"])\nws.set_row(0, 26)\nws.write(1, 0, f\"Generated {pd.Timestamp.now():%Y-%m-%d %H:%M} · \"\n               f\"{len(data):,} rows\", fmt[\"note\"])\n\nfor col, name in enumerate(HEADERS):\n    ws.write(FIRST_DATA_ROW, col, name, fmt[\"header\"])\nws.set_row(FIRST_DATA_ROW, 30)\n",[144,1014,1015,1052,1066,1070,1086,1096,1111,1115,1163,1177,1236,1266,1270,1291,1306],{"__ignoreMap":142},[147,1016,1017,1020,1023,1026,1029,1031,1034,1036,1039,1041,1044,1046,1049],{"class":149,"line":150},[147,1018,1019],{"class":270},"HEADERS",[147,1021,1022],{"class":190}," =",[147,1024,1025],{"class":194}," [",[147,1027,1028],{"class":157},"\"Region\"",[147,1030,243],{"class":194},[147,1032,1033],{"class":157},"\"Orders\"",[147,1035,243],{"class":194},[147,1037,1038],{"class":157},"\"Amount\"",[147,1040,243],{"class":194},[147,1042,1043],{"class":157},"\"Share\"",[147,1045,243],{"class":194},[147,1047,1048],{"class":157},"\"Updated\"",[147,1050,1051],{"class":194},"]\n",[147,1053,1054,1057,1059,1062],{"class":149,"line":198},[147,1055,1056],{"class":270},"FIRST_DATA_ROW",[147,1058,1022],{"class":190},[147,1060,1061],{"class":270}," 3",[147,1063,1065],{"class":1064},"s-wDw","                        # zero-based: title, note, blank, header at 3\n",[147,1067,1068],{"class":149,"line":212},[147,1069,216],{"emptyLinePlaceholder":215},[147,1071,1072,1075,1077,1080,1083],{"class":149,"line":219},[147,1073,1074],{"class":194},"wb ",[147,1076,225],{"class":190},[147,1078,1079],{"class":194}," xlsxwriter.Workbook(",[147,1081,1082],{"class":157},"\"regional-july.xlsx\"",[147,1084,1085],{"class":194},")\n",[147,1087,1088,1091,1093],{"class":149,"line":231},[147,1089,1090],{"class":194},"fmt ",[147,1092,225],{"class":190},[147,1094,1095],{"class":194}," make_formats(wb)\n",[147,1097,1098,1101,1103,1106,1109],{"class":149,"line":262},[147,1099,1100],{"class":194},"ws ",[147,1102,225],{"class":190},[147,1104,1105],{"class":194}," wb.add_worksheet(",[147,1107,1108],{"class":157},"\"Summary\"",[147,1110,1085],{"class":194},[147,1112,1113],{"class":149,"line":291},[147,1114,216],{"emptyLinePlaceholder":215},[147,1116,1117,1120,1122,1124,1126,1128,1130,1132,1135,1138,1140,1143,1146,1149,1151,1154,1157,1160],{"class":149,"line":319},[147,1118,1119],{"class":194},"ws.merge_range(",[147,1121,44],{"class":270},[147,1123,243],{"class":194},[147,1125,44],{"class":270},[147,1127,243],{"class":194},[147,1129,44],{"class":270},[147,1131,243],{"class":194},[147,1133,1134],{"class":270},"len",[147,1136,1137],{"class":194},"(",[147,1139,1019],{"class":270},[147,1141,1142],{"class":194},") ",[147,1144,1145],{"class":190},"-",[147,1147,1148],{"class":270}," 1",[147,1150,243],{"class":194},[147,1152,1153],{"class":157},"\"Regional sales — July 2026\"",[147,1155,1156],{"class":194},", fmt[",[147,1158,1159],{"class":157},"\"title\"",[147,1161,1162],{"class":194},"])\n",[147,1164,1165,1168,1170,1172,1175],{"class":149,"line":339},[147,1166,1167],{"class":194},"ws.set_row(",[147,1169,44],{"class":270},[147,1171,243],{"class":194},[147,1173,1174],{"class":270},"26",[147,1176,1085],{"class":194},[147,1178,1179,1182,1184,1186,1188,1190,1193,1196,1200,1203,1206,1209,1212,1215,1217,1220,1222,1225,1227,1230,1233],{"class":149,"line":353},[147,1180,1181],{"class":194},"ws.write(",[147,1183,541],{"class":270},[147,1185,243],{"class":194},[147,1187,44],{"class":270},[147,1189,243],{"class":194},[147,1191,1192],{"class":190},"f",[147,1194,1195],{"class":157},"\"Generated ",[147,1197,1199],{"class":1198},"sSjpA","{",[147,1201,1202],{"class":194},"pd.Timestamp.now():",[147,1204,1205],{"class":190},"%",[147,1207,1208],{"class":194},"Y",[147,1210,1211],{"class":190},"-%",[147,1213,1214],{"class":194},"m",[147,1216,1211],{"class":190},[147,1218,1219],{"class":194},"d ",[147,1221,1205],{"class":190},[147,1223,1224],{"class":194},"H:",[147,1226,1205],{"class":190},[147,1228,1229],{"class":194},"M",[147,1231,1232],{"class":1198},"}",[147,1234,1235],{"class":157}," · \"\n",[147,1237,1238,1241,1244,1246,1248,1251,1254,1256,1259,1261,1264],{"class":149,"line":359},[147,1239,1240],{"class":190},"               f",[147,1242,1243],{"class":157},"\"",[147,1245,1199],{"class":1198},[147,1247,1134],{"class":270},[147,1249,1250],{"class":194},"(data)",[147,1252,1253],{"class":190},":,",[147,1255,1232],{"class":1198},[147,1257,1258],{"class":157}," rows\"",[147,1260,1156],{"class":194},[147,1262,1263],{"class":157},"\"note\"",[147,1265,1162],{"class":194},[147,1267,1268],{"class":149,"line":391},[147,1269,216],{"emptyLinePlaceholder":215},[147,1271,1272,1275,1278,1281,1284,1286,1288],{"class":149,"line":425},[147,1273,1274],{"class":190},"for",[147,1276,1277],{"class":194}," col, name ",[147,1279,1280],{"class":190},"in",[147,1282,1283],{"class":270}," enumerate",[147,1285,1137],{"class":194},[147,1287,1019],{"class":270},[147,1289,1290],{"class":194},"):\n",[147,1292,1293,1296,1298,1301,1304],{"class":149,"line":430},[147,1294,1295],{"class":194},"    ws.write(",[147,1297,1056],{"class":270},[147,1299,1300],{"class":194},", col, name, fmt[",[147,1302,1303],{"class":157},"\"header\"",[147,1305,1162],{"class":194},[147,1307,1308,1310,1312,1314,1316],{"class":149,"line":435},[147,1309,1167],{"class":194},[147,1311,1056],{"class":270},[147,1313,243],{"class":194},[147,1315,82],{"class":270},[147,1317,1085],{"class":194},[10,1319,1320,1323,1324,1326,1327,1330],{},[144,1321,1322],{},"merge_range"," needs the format passed in the same call — a merged range written without one loses the alignment and fill. Keeping ",[144,1325,1056],{}," as a named constant rather than a literal ",[144,1328,1329],{},"3"," is what stops the autofilter, the freeze pane and the totals formula drifting out of alignment when you later add a line to the title block.",[132,1332,1334],{"id":1333},"step-3-write-the-data-with-banded-rows","Step 3: Write the data with banded rows",[10,1336,1337],{},"Alternate row shading is applied at write time by choosing the format per row:",[137,1339,1341],{"className":181,"code":1340,"language":183,"meta":142,"style":142},"COLUMN_FORMATS = [\"text\", \"int\", \"money\", \"pct\", \"date\"]\n\nfor i, row in enumerate(data.itertuples(index=False), start=0):\n    excel_row = FIRST_DATA_ROW + 1 + i\n    banded = i % 2 == 1\n    for col, (value, kind) in enumerate(zip(row, COLUMN_FORMATS)):\n        style = fmt[\"band\"] if (banded and kind == \"text\") else fmt[kind]\n        if kind == \"date\":\n            ws.write_datetime(excel_row, col, value, fmt[\"date\"])\n        elif kind == \"text\":\n            ws.write_string(excel_row, col, str(value), style)\n        else:\n            ws.write_number(excel_row, col, float(value), fmt[kind])\n",[144,1342,1343,1377,1381,1416,1436,1457,1482,1523,1538,1547,1560,1571,1578],{"__ignoreMap":142},[147,1344,1345,1348,1350,1352,1355,1357,1360,1362,1365,1367,1370,1372,1375],{"class":149,"line":150},[147,1346,1347],{"class":270},"COLUMN_FORMATS",[147,1349,1022],{"class":190},[147,1351,1025],{"class":194},[147,1353,1354],{"class":157},"\"text\"",[147,1356,243],{"class":194},[147,1358,1359],{"class":157},"\"int\"",[147,1361,243],{"class":194},[147,1363,1364],{"class":157},"\"money\"",[147,1366,243],{"class":194},[147,1368,1369],{"class":157},"\"pct\"",[147,1371,243],{"class":194},[147,1373,1374],{"class":157},"\"date\"",[147,1376,1051],{"class":194},[147,1378,1379],{"class":149,"line":198},[147,1380,216],{"emptyLinePlaceholder":215},[147,1382,1383,1385,1388,1390,1392,1395,1399,1401,1404,1407,1410,1412,1414],{"class":149,"line":212},[147,1384,1274],{"class":190},[147,1386,1387],{"class":194}," i, row ",[147,1389,1280],{"class":190},[147,1391,1283],{"class":270},[147,1393,1394],{"class":194},"(data.itertuples(",[147,1396,1398],{"class":1397},"sa561","index",[147,1400,225],{"class":190},[147,1402,1403],{"class":270},"False",[147,1405,1406],{"class":194},"), ",[147,1408,1409],{"class":1397},"start",[147,1411,225],{"class":190},[147,1413,44],{"class":270},[147,1415,1290],{"class":194},[147,1417,1418,1421,1423,1426,1429,1431,1433],{"class":149,"line":219},[147,1419,1420],{"class":194},"    excel_row ",[147,1422,225],{"class":190},[147,1424,1425],{"class":270}," FIRST_DATA_ROW",[147,1427,1428],{"class":190}," +",[147,1430,1148],{"class":270},[147,1432,1428],{"class":190},[147,1434,1435],{"class":194}," i\n",[147,1437,1438,1441,1443,1446,1448,1451,1454],{"class":149,"line":231},[147,1439,1440],{"class":194},"    banded ",[147,1442,225],{"class":190},[147,1444,1445],{"class":194}," i ",[147,1447,1205],{"class":190},[147,1449,1450],{"class":270}," 2",[147,1452,1453],{"class":190}," ==",[147,1455,1456],{"class":270}," 1\n",[147,1458,1459,1462,1465,1467,1469,1471,1474,1477,1479],{"class":149,"line":262},[147,1460,1461],{"class":190},"    for",[147,1463,1464],{"class":194}," col, (value, kind) ",[147,1466,1280],{"class":190},[147,1468,1283],{"class":270},[147,1470,1137],{"class":194},[147,1472,1473],{"class":270},"zip",[147,1475,1476],{"class":194},"(row, ",[147,1478,1347],{"class":270},[147,1480,1481],{"class":194},")):\n",[147,1483,1484,1487,1489,1492,1495,1497,1500,1503,1506,1509,1512,1515,1517,1520],{"class":149,"line":291},[147,1485,1486],{"class":194},"        style ",[147,1488,225],{"class":190},[147,1490,1491],{"class":194}," fmt[",[147,1493,1494],{"class":157},"\"band\"",[147,1496,368],{"class":194},[147,1498,1499],{"class":190},"if",[147,1501,1502],{"class":194}," (banded ",[147,1504,1505],{"class":190},"and",[147,1507,1508],{"class":194}," kind ",[147,1510,1511],{"class":190},"==",[147,1513,1514],{"class":157}," \"text\"",[147,1516,1142],{"class":194},[147,1518,1519],{"class":190},"else",[147,1521,1522],{"class":194}," fmt[kind]\n",[147,1524,1525,1528,1530,1532,1535],{"class":149,"line":319},[147,1526,1527],{"class":190},"        if",[147,1529,1508],{"class":194},[147,1531,1511],{"class":190},[147,1533,1534],{"class":157}," \"date\"",[147,1536,1537],{"class":194},":\n",[147,1539,1540,1543,1545],{"class":149,"line":339},[147,1541,1542],{"class":194},"            ws.write_datetime(excel_row, col, value, fmt[",[147,1544,1374],{"class":157},[147,1546,1162],{"class":194},[147,1548,1549,1552,1554,1556,1558],{"class":149,"line":353},[147,1550,1551],{"class":190},"        elif",[147,1553,1508],{"class":194},[147,1555,1511],{"class":190},[147,1557,1514],{"class":157},[147,1559,1537],{"class":194},[147,1561,1562,1565,1568],{"class":149,"line":359},[147,1563,1564],{"class":194},"            ws.write_string(excel_row, col, ",[147,1566,1567],{"class":270},"str",[147,1569,1570],{"class":194},"(value), style)\n",[147,1572,1573,1576],{"class":149,"line":391},[147,1574,1575],{"class":190},"        else",[147,1577,1537],{"class":194},[147,1579,1580,1583,1586],{"class":149,"line":425},[147,1581,1582],{"class":194},"            ws.write_number(excel_row, col, ",[147,1584,1585],{"class":270},"float",[147,1587,1588],{"class":194},"(value), fmt[kind])\n",[10,1590,1591,1592,243,1595,998,1598,1601,1602,1605,1606,1609,1610,1612],{},"Using the explicit ",[144,1593,1594],{},"write_string",[144,1596,1597],{},"write_number",[144,1599,1600],{},"write_datetime"," methods instead of the generic ",[144,1603,1604],{},"write"," removes xlsxwriter's type guessing, which matters for two cases in particular: an identifier like ",[144,1607,1608],{},"00123"," that ",[144,1611,1604],{}," would store as the number 123, and a date that arrives as a string and would be stored as text no reader can sort.",[10,1614,1615,1616,1619,1620,1624],{},"If you want full banding rather than banding on the label column only, build a second set of formats with ",[144,1617,1618],{},"bg_color"," set — every combination has to exist as its own format object, which is why an Excel ",[1621,1622,1623],"em",{},"table"," (below) is often the better answer.",[132,1626,1628],{"id":1627},"step-4-add-totals-that-survive-filtering","Step 4: Add totals that survive filtering",[10,1630,1631,1632,1634],{},"A totals row written as a plain number goes stale the moment a reader filters the table. ",[144,1633,130],{}," recalculates over visible rows only:",[137,1636,1638],{"className":181,"code":1637,"language":183,"meta":142,"style":142},"last_data_row = FIRST_DATA_ROW + len(data)          # zero-based index of the last row\ntotal_row = last_data_row + 1\nfirst, last = FIRST_DATA_ROW + 2, last_data_row + 1  # 1-based for the formula text\n\nws.write(total_row, 0, \"Total\", fmt[\"total_text\"])\nws.write_formula(total_row, 1, f\"=SUBTOTAL(109,B{first}:B{last})\",\n                 fmt[\"total_int\"], int(data[\"orders\"].sum()))\nws.write_formula(total_row, 2, f\"=SUBTOTAL(109,C{first}:C{last})\",\n                 fmt[\"total_money\"], float(data[\"amount\"].sum()))\nws.write_formula(total_row, 3, f\"=SUBTOTAL(109,D{first}:D{last})\",\n                 fmt[\"total_pct\"], float(data[\"share\"].sum()))\nws.write(total_row, 4, \"\", fmt[\"total_text\"])\n",[144,1639,1640,1660,1675,1698,1702,1721,1757,1779,1811,1828,1860,1877],{"__ignoreMap":142},[147,1641,1642,1645,1647,1649,1651,1654,1657],{"class":149,"line":150},[147,1643,1644],{"class":194},"last_data_row ",[147,1646,225],{"class":190},[147,1648,1425],{"class":270},[147,1650,1428],{"class":190},[147,1652,1653],{"class":270}," len",[147,1655,1656],{"class":194},"(data)          ",[147,1658,1659],{"class":1064},"# zero-based index of the last row\n",[147,1661,1662,1665,1667,1670,1673],{"class":149,"line":198},[147,1663,1664],{"class":194},"total_row ",[147,1666,225],{"class":190},[147,1668,1669],{"class":194}," last_data_row ",[147,1671,1672],{"class":190},"+",[147,1674,1456],{"class":270},[147,1676,1677,1680,1682,1684,1686,1688,1691,1693,1695],{"class":149,"line":212},[147,1678,1679],{"class":194},"first, last ",[147,1681,225],{"class":190},[147,1683,1425],{"class":270},[147,1685,1428],{"class":190},[147,1687,1450],{"class":270},[147,1689,1690],{"class":194},", last_data_row ",[147,1692,1672],{"class":190},[147,1694,1148],{"class":270},[147,1696,1697],{"class":1064},"  # 1-based for the formula text\n",[147,1699,1700],{"class":149,"line":219},[147,1701,216],{"emptyLinePlaceholder":215},[147,1703,1704,1707,1709,1711,1714,1716,1719],{"class":149,"line":231},[147,1705,1706],{"class":194},"ws.write(total_row, ",[147,1708,44],{"class":270},[147,1710,243],{"class":194},[147,1712,1713],{"class":157},"\"Total\"",[147,1715,1156],{"class":194},[147,1717,1718],{"class":157},"\"total_text\"",[147,1720,1162],{"class":194},[147,1722,1723,1726,1728,1730,1732,1735,1737,1740,1742,1745,1747,1750,1752,1755],{"class":149,"line":262},[147,1724,1725],{"class":194},"ws.write_formula(total_row, ",[147,1727,541],{"class":270},[147,1729,243],{"class":194},[147,1731,1192],{"class":190},[147,1733,1734],{"class":157},"\"=SUBTOTAL(109,B",[147,1736,1199],{"class":1198},[147,1738,1739],{"class":194},"first",[147,1741,1232],{"class":1198},[147,1743,1744],{"class":157},":B",[147,1746,1199],{"class":1198},[147,1748,1749],{"class":194},"last",[147,1751,1232],{"class":1198},[147,1753,1754],{"class":157},")\"",[147,1756,336],{"class":194},[147,1758,1759,1762,1765,1768,1771,1774,1776],{"class":149,"line":291},[147,1760,1761],{"class":194},"                 fmt[",[147,1763,1764],{"class":157},"\"total_int\"",[147,1766,1767],{"class":194},"], ",[147,1769,1770],{"class":270},"int",[147,1772,1773],{"class":194},"(data[",[147,1775,406],{"class":157},[147,1777,1778],{"class":194},"].sum()))\n",[147,1780,1781,1783,1785,1787,1789,1792,1794,1796,1798,1801,1803,1805,1807,1809],{"class":149,"line":319},[147,1782,1725],{"class":194},[147,1784,841],{"class":270},[147,1786,243],{"class":194},[147,1788,1192],{"class":190},[147,1790,1791],{"class":157},"\"=SUBTOTAL(109,C",[147,1793,1199],{"class":1198},[147,1795,1739],{"class":194},[147,1797,1232],{"class":1198},[147,1799,1800],{"class":157},":C",[147,1802,1199],{"class":1198},[147,1804,1749],{"class":194},[147,1806,1232],{"class":1198},[147,1808,1754],{"class":157},[147,1810,336],{"class":194},[147,1812,1813,1815,1818,1820,1822,1824,1826],{"class":149,"line":339},[147,1814,1761],{"class":194},[147,1816,1817],{"class":157},"\"total_money\"",[147,1819,1767],{"class":194},[147,1821,1585],{"class":270},[147,1823,1773],{"class":194},[147,1825,376],{"class":157},[147,1827,1778],{"class":194},[147,1829,1830,1832,1834,1836,1838,1841,1843,1845,1847,1850,1852,1854,1856,1858],{"class":149,"line":353},[147,1831,1725],{"class":194},[147,1833,1329],{"class":270},[147,1835,243],{"class":194},[147,1837,1192],{"class":190},[147,1839,1840],{"class":157},"\"=SUBTOTAL(109,D",[147,1842,1199],{"class":1198},[147,1844,1739],{"class":194},[147,1846,1232],{"class":1198},[147,1848,1849],{"class":157},":D",[147,1851,1199],{"class":1198},[147,1853,1749],{"class":194},[147,1855,1232],{"class":1198},[147,1857,1754],{"class":157},[147,1859,336],{"class":194},[147,1861,1862,1864,1867,1869,1871,1873,1875],{"class":149,"line":359},[147,1863,1761],{"class":194},[147,1865,1866],{"class":157},"\"total_pct\"",[147,1868,1767],{"class":194},[147,1870,1585],{"class":270},[147,1872,1773],{"class":194},[147,1874,365],{"class":157},[147,1876,1778],{"class":194},[147,1878,1879,1881,1883,1885,1888,1890,1892],{"class":149,"line":391},[147,1880,1706],{"class":194},[147,1882,70],{"class":270},[147,1884,243],{"class":194},[147,1886,1887],{"class":157},"\"\"",[147,1889,1156],{"class":194},[147,1891,1718],{"class":157},[147,1893,1162],{"class":194},[23,1895,32,1900,32,1903,32,1906,32,1909,32,1914,32,1922,32,1928,32,1933,32,1938,32,1942,32,1947,32,1951,32,1953,32,1956,32,1959,32,1962,32,1967,32,1970,32,1974],{"viewBox":1896,"role":26,"ariaLabelledBy":1897,"xmlns":30,"style":31},"0 0 740 218",[1898,1899],"fw-total-t","fw-total-d",[34,1901,1902],{"id":1898},"What each kind of total does when the reader filters to one region",[38,1904,1905],{"id":1899},"A number computed in Python and written as a value never changes, so after filtering it contradicts the visible rows. A plain SUM recalculates but still counts the hidden rows. SUBTOTAL with function 109 counts only what is visible, so the total always matches the rows on screen.",[42,1907],{"x":44,"y":44,"width":45,"height":1908,"fill":47},"218",[57,1910,1913],{"x":1911,"y":82,"style":1912},"370","font-size:12.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Filtered to South: visible rows total 274.75",[42,1915],{"x":1916,"y":1917,"width":1918,"height":1919,"rx":482,"fill":1920,"stroke":1921,"style":122},"16","50","228","146","#fce9e9","var(--accent-ink,#be185d)",[57,1923,1927],{"x":1924,"y":1925,"style":1926},"130","78","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","a written value",[57,1929,1932],{"x":1924,"y":1930,"style":1931},"108","font-size:16px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","500.00",[57,1934,1937],{"x":1924,"y":1935,"style":1936},"140","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","frozen at generation time",[57,1939,1941],{"x":1924,"y":1940,"style":1936},"164","contradicts the screen",[42,1943],{"x":1944,"y":1917,"width":1918,"height":1919,"rx":482,"fill":1945,"stroke":1946,"style":122},"256","#fdefd8","var(--gold,#b4740a)",[57,1948,1950],{"x":1911,"y":1925,"style":1949},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","=SUM(C2:C5)",[57,1952,1932],{"x":1911,"y":1930,"style":1931},[57,1954,1955],{"x":1911,"y":1935,"style":1936},"recalculates, but counts",[57,1957,1958],{"x":1911,"y":1940,"style":1936},"the hidden rows too",[42,1960],{"x":1961,"y":1917,"width":1918,"height":1919,"rx":482,"fill":120,"stroke":121,"style":122},"496",[57,1963,1966],{"x":1964,"y":1925,"style":1965},"610","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","=SUBTOTAL(109,C2:C5)",[57,1968,299],{"x":1964,"y":1930,"style":1969},"font-size:16px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[57,1971,1973],{"x":1964,"y":1935,"style":1972},"font-size:11px;fill:var(--text,#172033);text-anchor:middle","counts visible rows only",[57,1975,1976],{"x":1964,"y":1940,"style":1936},"matches what is on screen",[10,1978,1979,1980,1983,1984,1987,1988,1996,1997,2000],{},"Function 109 is ",[144,1981,1982],{},"SUM"," ignoring hidden rows; 9 would include them. The fourth argument to ",[144,1985,1986],{},"write_formula"," is the cached result: xlsxwriter does not evaluate formulas, so without it any tool that reads the file without opening Excel — including ",[17,1989,1991,1992,1995],{"href":1990},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Fread-formula-results-with-openpyxl-data-only\u002F","openpyxl in ",[144,1993,1994],{},"data_only"," mode"," and a downstream pandas read — sees ",[144,1998,1999],{},"None"," where the total should be.",[132,2002,2004],{"id":2003},"step-5-widths-filter-and-panes","Step 5: Widths, filter and panes",[10,2006,2007],{},"The finishing pass is three calls, and the width calculation is the only one with any logic in it:",[137,2009,2011],{"className":181,"code":2010,"language":183,"meta":142,"style":142},"from itertools import chain\n\n\ndef column_width(series, heading, pad=3, cap=42):\n    longest = max(chain([len(str(heading))],\n                        (len(str(v)) for v in series)))\n    return min(longest + pad, cap)\n\n\nfor col, name in enumerate(data.columns):\n    width = column_width(data[name], HEADERS[col])\n    default = {\"region\": None, \"orders\": fmt[\"int\"], \"amount\": fmt[\"money\"],\n               \"share\": fmt[\"pct\"], \"updated\": fmt[\"date\"]}[name]\n    ws.set_column(col, col, width, default)\n\nws.autofilter(FIRST_DATA_ROW, 0, last_data_row, len(HEADERS) - 1)\nws.freeze_panes(FIRST_DATA_ROW + 1, 0)\nws.set_landscape()\nws.fit_to_pages(1, 0)                 # one page wide, any number tall\nwb.close()\n",[144,2012,2013,2026,2030,2034,2058,2080,2104,2119,2123,2127,2140,2155,2190,2210,2215,2219,2247,2264,2269,2286],{"__ignoreMap":142},[147,2014,2015,2018,2021,2023],{"class":149,"line":150},[147,2016,2017],{"class":190},"from",[147,2019,2020],{"class":194}," itertools ",[147,2022,191],{"class":190},[147,2024,2025],{"class":194}," chain\n",[147,2027,2028],{"class":149,"line":198},[147,2029,216],{"emptyLinePlaceholder":215},[147,2031,2032],{"class":149,"line":212},[147,2033,216],{"emptyLinePlaceholder":215},[147,2035,2036,2038,2041,2044,2046,2048,2051,2053,2056],{"class":149,"line":219},[147,2037,438],{"class":190},[147,2039,2040],{"class":441}," column_width",[147,2042,2043],{"class":194},"(series, heading, pad",[147,2045,225],{"class":190},[147,2047,1329],{"class":270},[147,2049,2050],{"class":194},", cap",[147,2052,225],{"class":190},[147,2054,2055],{"class":270},"42",[147,2057,1290],{"class":194},[147,2059,2060,2063,2065,2068,2071,2073,2075,2077],{"class":149,"line":231},[147,2061,2062],{"class":194},"    longest ",[147,2064,225],{"class":190},[147,2066,2067],{"class":270}," max",[147,2069,2070],{"class":194},"(chain([",[147,2072,1134],{"class":270},[147,2074,1137],{"class":194},[147,2076,1567],{"class":270},[147,2078,2079],{"class":194},"(heading))],\n",[147,2081,2082,2085,2087,2089,2091,2094,2096,2099,2101],{"class":149,"line":262},[147,2083,2084],{"class":194},"                        (",[147,2086,1134],{"class":270},[147,2088,1137],{"class":194},[147,2090,1567],{"class":270},[147,2092,2093],{"class":194},"(v)) ",[147,2095,1274],{"class":190},[147,2097,2098],{"class":194}," v ",[147,2100,1280],{"class":190},[147,2102,2103],{"class":194}," series)))\n",[147,2105,2106,2108,2111,2114,2116],{"class":149,"line":291},[147,2107,451],{"class":190},[147,2109,2110],{"class":270}," min",[147,2112,2113],{"class":194},"(longest ",[147,2115,1672],{"class":190},[147,2117,2118],{"class":194}," pad, cap)\n",[147,2120,2121],{"class":149,"line":319},[147,2122,216],{"emptyLinePlaceholder":215},[147,2124,2125],{"class":149,"line":339},[147,2126,216],{"emptyLinePlaceholder":215},[147,2128,2129,2131,2133,2135,2137],{"class":149,"line":353},[147,2130,1274],{"class":190},[147,2132,1277],{"class":194},[147,2134,1280],{"class":190},[147,2136,1283],{"class":270},[147,2138,2139],{"class":194},"(data.columns):\n",[147,2141,2142,2145,2147,2150,2152],{"class":149,"line":359},[147,2143,2144],{"class":194},"    width ",[147,2146,225],{"class":190},[147,2148,2149],{"class":194}," column_width(data[name], ",[147,2151,1019],{"class":270},[147,2153,2154],{"class":194},"[col])\n",[147,2156,2157,2160,2162,2165,2167,2169,2171,2173,2175,2178,2180,2182,2184,2186,2188],{"class":149,"line":391},[147,2158,2159],{"class":194},"    default ",[147,2161,225],{"class":190},[147,2163,2164],{"class":194}," {",[147,2166,401],{"class":157},[147,2168,469],{"class":194},[147,2170,1999],{"class":270},[147,2172,243],{"class":194},[147,2174,406],{"class":157},[147,2176,2177],{"class":194},": fmt[",[147,2179,1359],{"class":157},[147,2181,1767],{"class":194},[147,2183,376],{"class":157},[147,2185,2177],{"class":194},[147,2187,1364],{"class":157},[147,2189,259],{"class":194},[147,2191,2192,2195,2197,2199,2201,2203,2205,2207],{"class":149,"line":425},[147,2193,2194],{"class":157},"               \"share\"",[147,2196,2177],{"class":194},[147,2198,1369],{"class":157},[147,2200,1767],{"class":194},[147,2202,419],{"class":157},[147,2204,2177],{"class":194},[147,2206,1374],{"class":157},[147,2208,2209],{"class":194},"]}[name]\n",[147,2211,2212],{"class":149,"line":430},[147,2213,2214],{"class":194},"    ws.set_column(col, col, width, default)\n",[147,2216,2217],{"class":149,"line":435},[147,2218,216],{"emptyLinePlaceholder":215},[147,2220,2221,2224,2226,2228,2230,2233,2235,2237,2239,2241,2243,2245],{"class":149,"line":448},[147,2222,2223],{"class":194},"ws.autofilter(",[147,2225,1056],{"class":270},[147,2227,243],{"class":194},[147,2229,44],{"class":270},[147,2231,2232],{"class":194},", last_data_row, ",[147,2234,1134],{"class":270},[147,2236,1137],{"class":194},[147,2238,1019],{"class":270},[147,2240,1142],{"class":194},[147,2242,1145],{"class":190},[147,2244,1148],{"class":270},[147,2246,1085],{"class":194},[147,2248,2249,2252,2254,2256,2258,2260,2262],{"class":149,"line":457},[147,2250,2251],{"class":194},"ws.freeze_panes(",[147,2253,1056],{"class":270},[147,2255,1428],{"class":190},[147,2257,1148],{"class":270},[147,2259,243],{"class":194},[147,2261,44],{"class":270},[147,2263,1085],{"class":194},[147,2265,2266],{"class":149,"line":487},[147,2267,2268],{"class":194},"ws.set_landscape()\n",[147,2270,2271,2274,2276,2278,2280,2283],{"class":149,"line":510},[147,2272,2273],{"class":194},"ws.fit_to_pages(",[147,2275,541],{"class":270},[147,2277,243],{"class":194},[147,2279,44],{"class":270},[147,2281,2282],{"class":194},")                 ",[147,2284,2285],{"class":1064},"# one page wide, any number tall\n",[147,2287,2288],{"class":149,"line":533},[147,2289,2290],{"class":194},"wb.close()\n",[10,2292,2293,2294,2296],{},"There is no autofit in the ",[144,2295,170],{}," format — Excel computes widths when a user double-clicks a column border, and a generated file has to state them. The cap is what stops one long free-text value making a column unusably wide; the pad accounts for the header's centre alignment and the filter arrow.",[10,2298,2299,2302,2303,2307],{},[144,2300,2301],{},"fit_to_pages(1, 0)"," is worth setting even for a file nobody plans to print, because \"print to PDF\" is how these reports are circulated to people who never open Excel. The page-setup details are covered in ",[17,2304,2306],{"href":2305},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002F","Exporting Excel Reports to PDF",".",[23,2309,32,2314,32,2317,32,2320,32,2323,32,2327,32,2335,32,2341,32,2345,32,2347,32,2351,32,2354,32,2357,32,2362,32,2365,32,2368,32,2372,32,2375,32,2379,32,2384,32,2387,32,2393,32,2398,32,2402,32,2405,32,2409,32,2412,32,2415,32,2418,32,2422,32,2427,32,2432],{"viewBox":2310,"role":26,"ariaLabelledBy":2311,"xmlns":30,"style":31},"0 0 740 244",[2312,2313],"fw-order-t","fw-order-d",[34,2315,2316],{"id":2312},"The order the sheet has to be built in",[38,2318,2319],{"id":2313},"Formats are created first, then column widths and defaults, then the title block, the header row and the data, then the totals row, and finally the sheet-level settings — autofilter, freeze panes and page setup. Because a written cell cannot be restyled, anything decided late has to be applied through set_column or written with the right format the first time.",[42,2321],{"x":44,"y":44,"width":45,"height":2322,"fill":47},"244",[57,2324,2326],{"x":1911,"y":82,"style":2325},"font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A written cell cannot be restyled — so the order is the design",[42,2328],{"x":1916,"y":2329,"width":2330,"height":2331,"rx":2332,"fill":2333,"stroke":2334,"style":122},"52","136","72","12","#ebebfd","var(--brand,#5b5cf0)",[57,2336,2340],{"x":2337,"y":2338,"style":2339},"84","80","font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","1. formats",[57,2342,2344],{"x":2337,"y":2343,"style":1936},"102","created once",[42,2346],{"x":1940,"y":2329,"width":2330,"height":2331,"rx":2332,"fill":2333,"stroke":2334,"style":122},[57,2348,2350],{"x":2349,"y":2338,"style":2339},"232","2. set_column",[57,2352,2353],{"x":2349,"y":2343,"style":1936},"width + default",[42,2355],{"x":2356,"y":2329,"width":2330,"height":2331,"rx":2332,"fill":1945,"stroke":1946,"style":122},"312",[57,2358,2361],{"x":2359,"y":2338,"style":2360},"380","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","3. title + header",[57,2363,2364],{"x":2359,"y":2343,"style":1936},"merged, then row 4",[42,2366],{"x":2367,"y":2329,"width":2330,"height":2331,"rx":2332,"fill":1945,"stroke":1946,"style":122},"460",[57,2369,2371],{"x":2370,"y":2338,"style":2360},"528","4. data + totals",[57,2373,2374],{"x":2370,"y":2343,"style":1936},"typed writes",[42,2376],{"x":2377,"y":2329,"width":2378,"height":2331,"rx":2332,"fill":120,"stroke":121,"style":122},"608","116",[57,2380,2383],{"x":2381,"y":2338,"style":2382},"666","font-size:11.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","5. sheet",[57,2385,2386],{"x":2381,"y":2343,"style":1936},"filter · panes",[149,2388],{"x1":2389,"y1":2390,"x2":2391,"y2":2390,"stroke":2392,"style":122},"156","88","160","var(--muted,#5b6780)",[2394,2395],"polygon",{"points":2396,"fill":2397},"164,88 154,83 154,93","#5b6780",[149,2399],{"x1":2400,"y1":2390,"x2":2401,"y2":2390,"stroke":2392,"style":122},"304","308",[2394,2403],{"points":2404,"fill":2397},"312,88 302,83 302,93",[149,2406],{"x1":2407,"y1":2390,"x2":2408,"y2":2390,"stroke":2392,"style":122},"452","456",[2394,2410],{"points":2411,"fill":2397},"460,88 450,83 450,93",[149,2413],{"x1":52,"y1":2390,"x2":2414,"y2":2390,"stroke":2392,"style":122},"604",[2394,2416],{"points":2417,"fill":2397},"608,88 598,83 598,93",[42,2419],{"x":1916,"y":1919,"width":2420,"height":2421,"rx":2332,"fill":1920,"stroke":1921},"708","76",[57,2423,2426],{"x":50,"y":2424,"style":2425},"172","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d)","The two decisions that cannot be deferred",[57,2428,2431],{"x":50,"y":2429,"style":2430},"196","font-size:11.5px;fill:var(--text,#172033)","Column widths, because measuring the data after writing it is too late to apply the result to the cells.",[57,2433,2435],{"x":50,"y":2434,"style":2430},"214","Header formatting, because pandas will write plain header cells if you let it — so write the frame with header=False.",[132,2437,2439],{"id":2438},"step-6-the-pandas-hand-off","Step 6: The pandas hand-off",[10,2441,2442],{},"When the data is already a DataFrame, let pandas write the body and take over for everything else:",[137,2444,2446],{"className":181,"code":2445,"language":183,"meta":142,"style":142},"with pd.ExcelWriter(\"regional-july.xlsx\", engine=\"xlsxwriter\",\n                    datetime_format=\"yyyy-mm-dd\") as writer:\n    data.to_excel(writer, sheet_name=\"Summary\", index=False,\n                  header=False, startrow=FIRST_DATA_ROW + 1)\n    wb, ws = writer.book, writer.sheets[\"Summary\"]\n    fmt = make_formats(wb)\n    # ... title, header row, widths, totals, filter and panes exactly as above\n",[144,2447,2448,2470,2486,2508,2532,2546,2555],{"__ignoreMap":142},[147,2449,2450,2453,2456,2458,2460,2463,2465,2468],{"class":149,"line":150},[147,2451,2452],{"class":190},"with",[147,2454,2455],{"class":194}," pd.ExcelWriter(",[147,2457,1082],{"class":157},[147,2459,243],{"class":194},[147,2461,2462],{"class":1397},"engine",[147,2464,225],{"class":190},[147,2466,2467],{"class":157},"\"xlsxwriter\"",[147,2469,336],{"class":194},[147,2471,2472,2475,2477,2479,2481,2483],{"class":149,"line":198},[147,2473,2474],{"class":1397},"                    datetime_format",[147,2476,225],{"class":190},[147,2478,782],{"class":157},[147,2480,1142],{"class":194},[147,2482,206],{"class":190},[147,2484,2485],{"class":194}," writer:\n",[147,2487,2488,2491,2494,2496,2498,2500,2502,2504,2506],{"class":149,"line":212},[147,2489,2490],{"class":194},"    data.to_excel(writer, ",[147,2492,2493],{"class":1397},"sheet_name",[147,2495,225],{"class":190},[147,2497,1108],{"class":157},[147,2499,243],{"class":194},[147,2501,1398],{"class":1397},[147,2503,225],{"class":190},[147,2505,1403],{"class":270},[147,2507,336],{"class":194},[147,2509,2510,2513,2515,2517,2519,2522,2524,2526,2528,2530],{"class":149,"line":219},[147,2511,2512],{"class":1397},"                  header",[147,2514,225],{"class":190},[147,2516,1403],{"class":270},[147,2518,243],{"class":194},[147,2520,2521],{"class":1397},"startrow",[147,2523,225],{"class":190},[147,2525,1056],{"class":270},[147,2527,1428],{"class":190},[147,2529,1148],{"class":270},[147,2531,1085],{"class":194},[147,2533,2534,2537,2539,2542,2544],{"class":149,"line":231},[147,2535,2536],{"class":194},"    wb, ws ",[147,2538,225],{"class":190},[147,2540,2541],{"class":194}," writer.book, writer.sheets[",[147,2543,1108],{"class":157},[147,2545,1051],{"class":194},[147,2547,2548,2551,2553],{"class":149,"line":262},[147,2549,2550],{"class":194},"    fmt ",[147,2552,225],{"class":190},[147,2554,1095],{"class":194},[147,2556,2557],{"class":149,"line":291},[147,2558,2559],{"class":1064},"    # ... title, header row, widths, totals, filter and panes exactly as above\n",[10,2561,2562,2565,2566,2568],{},[144,2563,2564],{},"header=False"," plus ",[144,2567,2521],{}," is the whole trick: pandas writes only the values, and every styled cell — title, header, totals — is written by you with a format you chose. Trying it the other way round leaves the header in pandas' default bold and no way to change it.",[132,2570,2572],{"id":2571},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[1623,2574,2575,2591],{},[2576,2577,2578],"thead",{},[2579,2580,2581,2585,2588],"tr",{},[2582,2583,2584],"th",{},"Symptom",[2582,2586,2587],{},"Cause",[2582,2589,2590],{},"Fix",[2592,2593,2594,2612,2625,2638,2652,2669,2683,2699],"tbody",{},[2579,2595,2596,2600,2606],{},[2597,2598,2599],"td",{},"The file is empty or missing",[2597,2601,2602,2605],{},[144,2603,2604],{},"close()"," never ran",[2597,2607,2608,2609],{},"Use ",[144,2610,2611],{},"with xlsxwriter.Workbook(path) as wb:",[2579,2613,2614,2617,2620],{},[2597,2615,2616],{},"Header still plain after styling",[2597,2618,2619],{},"pandas wrote those cells first",[2597,2621,2622,2624],{},[144,2623,2564],{},", write the header yourself",[2579,2626,2627,2630,2635],{},[2597,2628,2629],{},"A merged title lost its fill",[2597,2631,2632,2633],{},"Format not passed to ",[144,2634,1322],{},[2597,2636,2637],{},"Pass the format in the same call",[2579,2639,2640,2643,2648],{},[2597,2641,2642],{},"Leading zeros disappeared",[2597,2644,2645,2647],{},[144,2646,1604],{}," guessed a number",[2597,2649,2608,2650],{},[144,2651,1594],{},[2579,2653,2654,2657,2660],{},[2597,2655,2656],{},"Dates appear as serial numbers",[2597,2658,2659],{},"Written without a date format",[2597,2661,2662,2664,2665,2668],{},[144,2663,1600],{}," with a ",[144,2666,2667],{},"num_format"," format",[2579,2670,2671,2677,2680],{},[2597,2672,2673,2674,2676],{},"Totals read as ",[144,2675,1999],{}," elsewhere",[2597,2678,2679],{},"No cached value on the formula",[2597,2681,2682],{},"Pass the computed result as the 4th argument",[2579,2684,2685,2688,2694],{},[2597,2686,2687],{},"Total wrong after filtering",[2597,2689,2690,2691,2693],{},"Plain ",[144,2692,1982],{}," includes hidden rows",[2597,2695,2696],{},[144,2697,2698],{},"SUBTOTAL(109, ...)",[2579,2700,2701,2704,2707],{},[2597,2702,2703],{},"File is huge for its row count",[2597,2705,2706],{},"A format created inside the loop",[2597,2708,2709],{},"Create formats once, outside",[132,2711,2713],{"id":2712},"performance-and-scale-notes","Performance and scale notes",[10,2715,2716,2717,2720],{},"Format reuse is the dominant cost at scale. A workbook that creates a format per cell can be several times larger and noticeably slower to write than one reusing a dozen objects, because every distinct format becomes a style record in the file. ",[144,2718,2719],{},"set_column"," is the cheapest formatting available — one record for a whole column — so push as much as possible there and reserve per-cell formats for genuine exceptions.",[10,2722,2723,2724,2727,2728,2732],{},"Beyond about half a million rows, switch the workbook to ",[144,2725,2726],{},"constant_memory"," mode and accept the strict write order; ",[17,2729,2731],{"href":2730},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory\u002F","Write a Million Rows to Excel with xlsxwriter"," covers what that changes. Below that, the default mode is faster because it avoids the temporary files.",[132,2734,2736],{"id":2735},"conclusion","Conclusion",[10,2738,2739,2740,2742,2743,2745],{},"Build the sheet in the order xlsxwriter wants and the report comes out right the first time: formats defined once, column widths and defaults set before any data is written, a title block and a header row you control, typed writes for the data, ",[144,2741,130],{}," formulas with cached values for the totals, and the sheet-level settings last. When the data starts as a DataFrame, write it headerless with ",[144,2744,2521],{}," and keep every styled cell in your own hands.",[132,2747,2749],{"id":2748},"frequently-asked-questions","Frequently asked questions",[10,2751,2752,2756,2757,998,2759,2762],{},[2753,2754,2755],"strong",{},"Why does my header keep the plain pandas styling?","\npandas wrote those cells first, and xlsxwriter cannot restyle a written cell. Write the frame with ",[144,2758,2564],{},[144,2760,2761],{},"startrow=1",", then write your own header row with the format you want.",[10,2764,2765,2768,2769,2771],{},[2753,2766,2767],{},"How do I autofit column widths?","\nThere is no autofit in the file format. Measure the longest string in each column and pass that length plus a small pad to ",[144,2770,2719],{},", with a cap so one long comment cannot push a column off the screen.",[10,2773,2774,2777,2778,2780],{},[2753,2775,2776],{},"Should the total row be a formula or a computed value?","\nA formula, written with ",[144,2779,1986],{},", so it stays correct if a reader filters or edits rows. Include the computed result as the optional value argument so tools that never open Excel still see a number.",[10,2782,2783,2786,2787,2789],{},[2753,2784,2785],{},"Can I apply a format to a cell after writing it?","\nNo. Decide the format at write time, or set a default for the whole column with ",[144,2788,2719],{}," before the data is written.",[132,2791,2793],{"id":2792},"related","Related",[10,2795,2796],{},"Up to the parent guide:",[2798,2799,2800],"ul",{},[2801,2802,2803,2805],"li",{},[17,2804,20],{"href":19}," — the engine's model and when to choose it.",[10,2807,2808],{},"Related guides:",[2798,2810,2811,2818,2825,2832],{},[2801,2812,2813,2817],{},[17,2814,2816],{"href":2815},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fadd-a-chart-to-an-excel-file-with-xlsxwriter\u002F","Add a Chart to an Excel File with xlsxwriter"," — the next thing this sheet wants.",[2801,2819,2820,2824],{},[17,2821,2823],{"href":2822},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002F","Apply Conditional Formatting with xlsxwriter"," — rules that keep working as readers edit.",[2801,2826,2827,2831],{},[17,2828,2830],{"href":2829},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl\u002F","Set Column Width and Row Height in openpyxl"," — the same measurement problem on the other engine.",[2801,2833,2834,2838],{},[17,2835,2837],{"href":2836},"\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"," — banded rows and filters as a table object instead of by hand.",[2840,2841,2842],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":142,"searchDepth":198,"depth":198,"links":2844},[2845,2846,2847,2848,2849,2850,2851,2852,2853,2854,2855,2856],{"id":134,"depth":198,"text":135},{"id":174,"depth":198,"text":175},{"id":1005,"depth":198,"text":1006},{"id":1333,"depth":198,"text":1334},{"id":1627,"depth":198,"text":1628},{"id":2003,"depth":198,"text":2004},{"id":2438,"depth":198,"text":2439},{"id":2571,"depth":198,"text":2572},{"id":2712,"depth":198,"text":2713},{"id":2735,"depth":198,"text":2736},{"id":2748,"depth":198,"text":2749},{"id":2792,"depth":198,"text":2793},"2026-08-10","Build a complete styled report in one pass: a title block, a header row you control, column formats and widths, a totals row, banded rows, an autofilter and frozen panes.","md",[2861,2863,2865,2867],{"q":2755,"a":2862},"pandas wrote those cells first, and xlsxwriter cannot restyle a written cell. Write the frame with header=False and startrow=1, then write your own header row with the format you want.",{"q":2767,"a":2864},"There is no autofit in the file format. Measure the longest string in each column and pass that length plus a small pad to set_column, with a cap so one long comment cannot push a column off the screen.",{"q":2776,"a":2866},"A formula, written with write_formula, so it stays correct if a reader filters or edits rows. Include the computed result as the optional value argument so tools that never open Excel still see a number.",{"q":2785,"a":2868},"No. Decide the format at write time, or set a default for the whole column with set_column before the data is written.",{"breadcrumb":2870},[2871,2873,2876,2877],{"name":2872,"item":381},"Home",{"name":2874,"item":2875},"Formatting and Charting Excel Reports with Python","\u002Fformatting-and-charting-excel-reports-with-python\u002F",{"name":20,"item":19},{"name":5,"item":2878},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter",{"title":5,"description":2881},"Generate a polished .xlsx report with xlsxwriter — reusable format objects, set_column widths and number formats, totals, banded rows, autofilter, freeze panes and a pandas hand-off.","write-a-formatted-excel-report-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002Findex","how-to","2AfJXnhVrvhTXJtAzrZmUVo5aA13i8u6x_FBixOQtmc",[2887,2890],{"title":2823,"path":2888,"stem":2889,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fapply-conditional-formatting-with-xlsxwriter\u002Findex",{"title":2731,"path":2891,"stem":2892,"children":-1},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory","formatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory\u002Findex",1786800027344]