[{"data":1,"prerenderedAt":2566},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas":2557},{"id":4,"title":5,"body":6,"dateModified":174,"datePublished":174,"description":2533,"extension":2534,"faq":2535,"meta":2548,"navigation":273,"path":2549,"seo":2550,"slug":2553,"stem":2554,"type":2555,"__hash__":2556},"docs\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas\u002Findex.md","Auto-Fit Column Widths When Writing with pandas",{"type":7,"value":8,"toc":2521},"minimark",[9,28,206,211,242,245,398,402,405,610,632,645,649,652,907,917,921,927,1403,1410,1420,1424,1520,1526,1870,1877,1881,2012,2016,2085,2092,2095,2164,2181,2191,2401,2409,2413,2425,2429,2438,2444,2459,2469,2475,2479,2517],[10,11,12,13,17,18,21,22,27],"p",{},"A pandas-written report opens with every column at Excel's default width. Headers are clipped, a ",[14,15,16],"code",{},"yyyy-mm-dd"," date column shows ",[14,19,20],{},"#####",", and the reader's first action is to select all and double-click a column border. Excel has a real auto-fit, but it runs in Excel — nothing pandas writes can trigger it, so the width has to be computed in Python and written into the file. This guide gives you a helper that does it properly, including the number-format cases that naive measurement gets wrong. It extends ",[23,24,26],"a",{"href":25},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002F","Writing DataFrames to Excel with pandas",".",[29,30,39,40,39,44,39,48,39,55,39,62,39,67,39,76,39,82,39,85,39,89,39,92,39,96,39,99,39,103,39,108,39,113,39,115,39,118,39,120,39,122,39,124,39,127,39,132,39,138,39,143,39,147,39,151,39,154,39,158,39,162,39,165,39,167,39,170,39,172,39,175,39,177,39,180,39,182,39,184,39,187,39,196,39,202],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"ariaLabelledBy":34,"xmlns":37,"style":38},"0 0 800 244","img","The same report at default widths and at fitted widths: default clips headers and shows hash marks for dates, while fitted widths display every value in full.",[35,36],"fit-t","fit-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:800px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[41,42,43],"title",{"id":35},"Default column widths against fitted ones",[45,46,47],"desc",{"id":36},"Two versions of the same four-column report. At Excel's default width the region name is clipped, the date column shows hash marks because the formatted date does not fit, and the header text is cut off mid-word. With widths computed from the content and the number format, every value and header displays in full, and the free-text column is capped so it cannot dominate the sheet.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","800","244","#ffffff",[56,57,61],"text",{"x":58,"y":59,"style":60},"196","28","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","default widths",[56,63,66],{"x":64,"y":59,"style":65},"600","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","fitted widths",[49,68],{"x":69,"y":70,"width":71,"height":59,"rx":72,"fill":73,"stroke":74,"style":75},"30","42","78","4","#fee8f2","var(--accent,#f43f8f)","stroke-width:2px",[56,77,81],{"x":78,"y":79,"style":80},"69","61","font-size:10px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","regi…",[49,83],{"x":84,"y":70,"width":71,"height":59,"rx":72,"fill":73,"stroke":74,"style":75},"112",[56,86,88],{"x":87,"y":79,"style":80},"151","invoi…",[49,90],{"x":91,"y":70,"width":71,"height":59,"rx":72,"fill":73,"stroke":74,"style":75},"194",[56,93,95],{"x":94,"y":79,"style":80},"233","reve…",[49,97],{"x":98,"y":70,"width":71,"height":59,"rx":72,"fill":73,"stroke":74,"style":75},"276",[56,100,102],{"x":101,"y":79,"style":80},"315","note",[49,104],{"x":69,"y":105,"width":71,"height":106,"rx":72,"fill":54,"stroke":107},"76","26","var(--line,#cdd5e6)",[56,109,112],{"x":78,"y":110,"style":111},"94","font-size:10px;fill:var(--text,#172033);text-anchor:middle","North…",[49,114],{"x":84,"y":105,"width":71,"height":106,"rx":72,"fill":54,"stroke":107},[56,116,117],{"x":87,"y":110,"style":80},"#######",[49,119],{"x":91,"y":105,"width":71,"height":106,"rx":72,"fill":54,"stroke":107},[56,121,117],{"x":94,"y":110,"style":80},[49,123],{"x":98,"y":105,"width":71,"height":106,"rx":72,"fill":54,"stroke":107},[56,125,126],{"x":101,"y":110,"style":111},"prov…",[56,128,131],{"x":58,"y":129,"style":130},"132","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","the reader's first action is to widen everything",[49,133],{"x":134,"y":70,"width":135,"height":59,"rx":72,"fill":136,"stroke":137,"style":75},"418","86","#d9f4f1","var(--teal,#0f9488)",[56,139,142],{"x":140,"y":79,"style":141},"461","font-size:10px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","region",[49,144],{"x":145,"y":70,"width":146,"height":59,"rx":72,"fill":136,"stroke":137,"style":75},"508","106",[56,148,150],{"x":149,"y":79,"style":141},"561","invoice_date",[49,152],{"x":153,"y":70,"width":135,"height":59,"rx":72,"fill":136,"stroke":137,"style":75},"618",[56,155,157],{"x":156,"y":79,"style":141},"661","revenue",[49,159],{"x":160,"y":70,"width":161,"height":59,"rx":72,"fill":136,"stroke":137,"style":75},"708","72",[56,163,102],{"x":164,"y":79,"style":141},"744",[49,166],{"x":134,"y":105,"width":135,"height":106,"rx":72,"fill":54,"stroke":107},[56,168,169],{"x":140,"y":110,"style":111},"North",[49,171],{"x":145,"y":105,"width":146,"height":106,"rx":72,"fill":54,"stroke":107},[56,173,174],{"x":149,"y":110,"style":111},"2026-08-15",[49,176],{"x":153,"y":105,"width":135,"height":106,"rx":72,"fill":54,"stroke":107},[56,178,179],{"x":156,"y":110,"style":111},"5,150.00",[49,181],{"x":160,"y":105,"width":161,"height":106,"rx":72,"fill":54,"stroke":107},[56,183,126],{"x":164,"y":110,"style":111},[56,185,186],{"x":64,"y":129,"style":130},"free-text column capped so it cannot dominate",[49,188],{"x":189,"y":190,"width":191,"height":192,"rx":193,"fill":194,"stroke":195,"style":75},"120","158","560","66","12","#fdefd8","var(--gold,#b4740a)",[56,197,201],{"x":198,"y":199,"style":200},"400","184","font-size:11.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","measure the FORMATTED width, not the raw value",[56,203,205],{"x":198,"y":204,"style":130},"208","46249 is five characters; \"2026-08-15\" is ten",[207,208,210],"h2",{"id":209},"prerequisites","Prerequisites",[212,213,218],"pre",{"className":214,"code":215,"language":216,"meta":217,"style":217},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas xlsxwriter openpyxl\n","bash","",[14,219,220],{"__ignoreMap":217},[221,222,225,229,233,236,239],"span",{"class":223,"line":224},"line",1,[221,226,228],{"class":227},"sMTad","pip",[221,230,232],{"class":231},"srMev"," install",[221,234,235],{"class":231}," pandas",[221,237,238],{"class":231}," xlsxwriter",[221,240,241],{"class":231}," openpyxl\n",[10,243,244],{},"A frame with the awkward cases — a long header, a date, a formatted number and a free-text column:",[212,246,250],{"className":247,"code":248,"language":249,"meta":217,"style":217},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.DataFrame({\n    \"region\": [\"North\", \"South-East Metropolitan\", \"West\"],\n    \"invoice_date\": pd.to_datetime([\"2026-08-15\", \"2026-08-16\", \"2026-08-17\"]),\n    \"revenue\": [5150.00, 4268.50, 3511.25],\n    \"note\": [\"provisional\",\n             \"restated after the Q2 close; see the reconciliation pack\",\n             \"\"],\n})\n","python",[14,251,252,268,275,287,313,338,362,376,384,392],{"__ignoreMap":217},[221,253,254,258,262,265],{"class":223,"line":224},[221,255,257],{"class":256},"s-kum","import",[221,259,261],{"class":260},"skGVy"," pandas ",[221,263,264],{"class":256},"as",[221,266,267],{"class":260}," pd\n",[221,269,271],{"class":223,"line":270},2,[221,272,274],{"emptyLinePlaceholder":273},true,"\n",[221,276,278,281,284],{"class":223,"line":277},3,[221,279,280],{"class":260},"df ",[221,282,283],{"class":256},"=",[221,285,286],{"class":260}," pd.DataFrame({\n",[221,288,290,293,296,299,302,305,307,310],{"class":223,"line":289},4,[221,291,292],{"class":231},"    \"region\"",[221,294,295],{"class":260},": [",[221,297,298],{"class":231},"\"North\"",[221,300,301],{"class":260},", ",[221,303,304],{"class":231},"\"South-East Metropolitan\"",[221,306,301],{"class":260},[221,308,309],{"class":231},"\"West\"",[221,311,312],{"class":260},"],\n",[221,314,316,319,322,325,327,330,332,335],{"class":223,"line":315},5,[221,317,318],{"class":231},"    \"invoice_date\"",[221,320,321],{"class":260},": pd.to_datetime([",[221,323,324],{"class":231},"\"2026-08-15\"",[221,326,301],{"class":260},[221,328,329],{"class":231},"\"2026-08-16\"",[221,331,301],{"class":260},[221,333,334],{"class":231},"\"2026-08-17\"",[221,336,337],{"class":260},"]),\n",[221,339,341,344,346,350,352,355,357,360],{"class":223,"line":340},6,[221,342,343],{"class":231},"    \"revenue\"",[221,345,295],{"class":260},[221,347,349],{"class":348},"sP0c6","5150.00",[221,351,301],{"class":260},[221,353,354],{"class":348},"4268.50",[221,356,301],{"class":260},[221,358,359],{"class":348},"3511.25",[221,361,312],{"class":260},[221,363,365,368,370,373],{"class":223,"line":364},7,[221,366,367],{"class":231},"    \"note\"",[221,369,295],{"class":260},[221,371,372],{"class":231},"\"provisional\"",[221,374,375],{"class":260},",\n",[221,377,379,382],{"class":223,"line":378},8,[221,380,381],{"class":231},"             \"restated after the Q2 close; see the reconciliation pack\"",[221,383,375],{"class":260},[221,385,387,390],{"class":223,"line":386},9,[221,388,389],{"class":231},"             \"\"",[221,391,312],{"class":260},[221,393,395],{"class":223,"line":394},10,[221,396,397],{"class":260},"})\n",[207,399,401],{"id":400},"step-1-measure-the-content","Step 1 — Measure the content",[10,403,404],{},"Width in Excel is measured in characters of the default font, so the length of the longest string in a column is a good estimate. Include the header, which is often the longest thing in the column:",[212,406,408],{"className":247,"code":407,"language":249,"meta":217,"style":217},"def column_widths(df, padding=2, min_width=8, max_width=50):\n    \"\"\"Estimate a display width in characters for each column.\"\"\"\n    widths = {}\n    for name in df.columns:\n        longest_value = (\n            df[name].astype(str).map(len).max() if len(df) else 0\n        )\n        widths[name] = max(\n            min_width,\n            min(max_width, max(int(longest_value), len(str(name))) + padding),\n        )\n    return widths\n\nprint(column_widths(df))\n# {'region': 25, 'invoice_date': 21, 'revenue': 9, 'note': 50}\n",[14,409,410,446,451,461,475,485,517,522,535,540,575,580,589,594,603],{"__ignoreMap":217},[221,411,412,415,419,422,424,427,430,432,435,438,440,443],{"class":223,"line":224},[221,413,414],{"class":256},"def",[221,416,418],{"class":417},"s_Opv"," column_widths",[221,420,421],{"class":260},"(df, padding",[221,423,283],{"class":256},[221,425,426],{"class":348},"2",[221,428,429],{"class":260},", min_width",[221,431,283],{"class":256},[221,433,434],{"class":348},"8",[221,436,437],{"class":260},", max_width",[221,439,283],{"class":256},[221,441,442],{"class":348},"50",[221,444,445],{"class":260},"):\n",[221,447,448],{"class":223,"line":270},[221,449,450],{"class":231},"    \"\"\"Estimate a display width in characters for each column.\"\"\"\n",[221,452,453,456,458],{"class":223,"line":277},[221,454,455],{"class":260},"    widths ",[221,457,283],{"class":256},[221,459,460],{"class":260}," {}\n",[221,462,463,466,469,472],{"class":223,"line":289},[221,464,465],{"class":256},"    for",[221,467,468],{"class":260}," name ",[221,470,471],{"class":256},"in",[221,473,474],{"class":260}," df.columns:\n",[221,476,477,480,482],{"class":223,"line":315},[221,478,479],{"class":260},"        longest_value ",[221,481,283],{"class":256},[221,483,484],{"class":260}," (\n",[221,486,487,490,493,496,499,502,505,508,511,514],{"class":223,"line":340},[221,488,489],{"class":260},"            df[name].astype(",[221,491,492],{"class":348},"str",[221,494,495],{"class":260},").map(",[221,497,498],{"class":348},"len",[221,500,501],{"class":260},").max() ",[221,503,504],{"class":256},"if",[221,506,507],{"class":348}," len",[221,509,510],{"class":260},"(df) ",[221,512,513],{"class":256},"else",[221,515,516],{"class":348}," 0\n",[221,518,519],{"class":223,"line":364},[221,520,521],{"class":260},"        )\n",[221,523,524,527,529,532],{"class":223,"line":378},[221,525,526],{"class":260},"        widths[name] ",[221,528,283],{"class":256},[221,530,531],{"class":348}," max",[221,533,534],{"class":260},"(\n",[221,536,537],{"class":223,"line":386},[221,538,539],{"class":260},"            min_width,\n",[221,541,542,545,548,551,554,557,560,562,564,566,569,572],{"class":223,"line":394},[221,543,544],{"class":348},"            min",[221,546,547],{"class":260},"(max_width, ",[221,549,550],{"class":348},"max",[221,552,553],{"class":260},"(",[221,555,556],{"class":348},"int",[221,558,559],{"class":260},"(longest_value), ",[221,561,498],{"class":348},[221,563,553],{"class":260},[221,565,492],{"class":348},[221,567,568],{"class":260},"(name))) ",[221,570,571],{"class":256},"+",[221,573,574],{"class":260}," padding),\n",[221,576,578],{"class":223,"line":577},11,[221,579,521],{"class":260},[221,581,583,586],{"class":223,"line":582},12,[221,584,585],{"class":256},"    return",[221,587,588],{"class":260}," widths\n",[221,590,592],{"class":223,"line":591},13,[221,593,274],{"emptyLinePlaceholder":273},[221,595,597,600],{"class":223,"line":596},14,[221,598,599],{"class":348},"print",[221,601,602],{"class":260},"(column_widths(df))\n",[221,604,606],{"class":223,"line":605},15,[221,607,609],{"class":608},"s-wDw","# {'region': 25, 'invoice_date': 21, 'revenue': 9, 'note': 50}\n",[10,611,612,613,616,617,620,621,624,625,627,628,631],{},"Three guards earn their place. ",[14,614,615],{},"min_width"," stops a column of single digits collapsing to something unusable. ",[14,618,619],{},"max_width"," stops the free-text column growing to fifty-six characters and pushing everything else off screen. And including ",[14,622,623],{},"len(str(name))"," covers the common case where the header is longer than any value — ",[14,626,150],{}," is twelve characters against a ",[14,629,630],{},"NaN","-free column of ten.",[10,633,634,637,638,641,642,644],{},[14,635,636],{},"df.astype(str)"," on a datetime column produces ",[14,639,640],{},"2026-08-15 00:00:00",", which is 19 characters — too wide for a column you intend to format as ",[14,643,16],{},". That is the next step.",[207,646,648],{"id":647},"step-2-size-from-the-number-format-not-the-value","Step 2 — Size from the number format, not the value",[10,650,651],{},"For any column you give a number format, the format string tells you the display width directly. That is more reliable than measuring the underlying value, which may be a serial number or a full timestamp:",[212,653,655],{"className":247,"code":654,"language":249,"meta":217,"style":217},"FORMAT_WIDTHS = {\n    \"yyyy-mm-dd\": 12,\n    \"yyyy-mm-dd hh:mm\": 19,\n    \"#,##0.00\": 12,\n    '\"$\"#,##0.00': 14,\n    \"0.0%\": 9,\n}\n\ndef fitted_widths(df, formats=None, **kwargs):\n    \"\"\"Widths from content, overridden by the display width of a number format.\"\"\"\n    widths = column_widths(df, **kwargs)\n    for name, fmt in (formats or {}).items():\n        if name in widths:\n            widths[name] = max(FORMAT_WIDTHS.get(fmt, 12), len(str(name)) + 2)\n    return widths\n\nFORMATS = {\"invoice_date\": \"yyyy-mm-dd\", \"revenue\": \"#,##0.00\"}\nprint(fitted_widths(df, FORMATS))\n# {'region': 25, 'invoice_date': 14, 'revenue': 12, 'note': 50}\n",[14,656,657,668,680,692,703,715,727,732,736,759,764,778,796,808,846,852,857,888,901],{"__ignoreMap":217},[221,658,659,662,665],{"class":223,"line":224},[221,660,661],{"class":348},"FORMAT_WIDTHS",[221,663,664],{"class":256}," =",[221,666,667],{"class":260}," {\n",[221,669,670,673,676,678],{"class":223,"line":270},[221,671,672],{"class":231},"    \"yyyy-mm-dd\"",[221,674,675],{"class":260},": ",[221,677,193],{"class":348},[221,679,375],{"class":260},[221,681,682,685,687,690],{"class":223,"line":277},[221,683,684],{"class":231},"    \"yyyy-mm-dd hh:mm\"",[221,686,675],{"class":260},[221,688,689],{"class":348},"19",[221,691,375],{"class":260},[221,693,694,697,699,701],{"class":223,"line":289},[221,695,696],{"class":231},"    \"#,##0.00\"",[221,698,675],{"class":260},[221,700,193],{"class":348},[221,702,375],{"class":260},[221,704,705,708,710,713],{"class":223,"line":315},[221,706,707],{"class":231},"    '\"$\"#,##0.00'",[221,709,675],{"class":260},[221,711,712],{"class":348},"14",[221,714,375],{"class":260},[221,716,717,720,722,725],{"class":223,"line":340},[221,718,719],{"class":231},"    \"0.0%\"",[221,721,675],{"class":260},[221,723,724],{"class":348},"9",[221,726,375],{"class":260},[221,728,729],{"class":223,"line":364},[221,730,731],{"class":260},"}\n",[221,733,734],{"class":223,"line":378},[221,735,274],{"emptyLinePlaceholder":273},[221,737,738,740,743,746,748,751,753,756],{"class":223,"line":386},[221,739,414],{"class":256},[221,741,742],{"class":417}," fitted_widths",[221,744,745],{"class":260},"(df, formats",[221,747,283],{"class":256},[221,749,750],{"class":348},"None",[221,752,301],{"class":260},[221,754,755],{"class":256},"**",[221,757,758],{"class":260},"kwargs):\n",[221,760,761],{"class":223,"line":394},[221,762,763],{"class":231},"    \"\"\"Widths from content, overridden by the display width of a number format.\"\"\"\n",[221,765,766,768,770,773,775],{"class":223,"line":577},[221,767,455],{"class":260},[221,769,283],{"class":256},[221,771,772],{"class":260}," column_widths(df, ",[221,774,755],{"class":256},[221,776,777],{"class":260},"kwargs)\n",[221,779,780,782,785,787,790,793],{"class":223,"line":582},[221,781,465],{"class":256},[221,783,784],{"class":260}," name, fmt ",[221,786,471],{"class":256},[221,788,789],{"class":260}," (formats ",[221,791,792],{"class":256},"or",[221,794,795],{"class":260}," {}).items():\n",[221,797,798,801,803,805],{"class":223,"line":591},[221,799,800],{"class":256},"        if",[221,802,468],{"class":260},[221,804,471],{"class":256},[221,806,807],{"class":260}," widths:\n",[221,809,810,813,815,817,819,821,824,826,829,831,833,835,838,840,843],{"class":223,"line":596},[221,811,812],{"class":260},"            widths[name] ",[221,814,283],{"class":256},[221,816,531],{"class":348},[221,818,553],{"class":260},[221,820,661],{"class":348},[221,822,823],{"class":260},".get(fmt, ",[221,825,193],{"class":348},[221,827,828],{"class":260},"), ",[221,830,498],{"class":348},[221,832,553],{"class":260},[221,834,492],{"class":348},[221,836,837],{"class":260},"(name)) ",[221,839,571],{"class":256},[221,841,842],{"class":348}," 2",[221,844,845],{"class":260},")\n",[221,847,848,850],{"class":223,"line":605},[221,849,585],{"class":256},[221,851,588],{"class":260},[221,853,855],{"class":223,"line":854},16,[221,856,274],{"emptyLinePlaceholder":273},[221,858,860,863,865,868,871,873,876,878,881,883,886],{"class":223,"line":859},17,[221,861,862],{"class":348},"FORMATS",[221,864,664],{"class":256},[221,866,867],{"class":260}," {",[221,869,870],{"class":231},"\"invoice_date\"",[221,872,675],{"class":260},[221,874,875],{"class":231},"\"yyyy-mm-dd\"",[221,877,301],{"class":260},[221,879,880],{"class":231},"\"revenue\"",[221,882,675],{"class":260},[221,884,885],{"class":231},"\"#,##0.00\"",[221,887,731],{"class":260},[221,889,891,893,896,898],{"class":223,"line":890},18,[221,892,599],{"class":348},[221,894,895],{"class":260},"(fitted_widths(df, ",[221,897,862],{"class":348},[221,899,900],{"class":260},"))\n",[221,902,904],{"class":223,"line":903},19,[221,905,906],{"class":608},"# {'region': 25, 'invoice_date': 14, 'revenue': 12, 'note': 50}\n",[10,908,909,910,912,913,27],{},"The ",[14,911,20],{}," display appears exactly when a formatted number is wider than its column, so sizing from the format eliminates that failure entirely — the wider treatment of which is in ",[23,914,916],{"href":915},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates\u002F","fixing Excel serial numbers showing instead of dates",[207,918,920],{"id":919},"step-3-write-the-widths-with-xlsxwriter","Step 3 — Write the widths with xlsxwriter",[10,922,923,926],{},[14,924,925],{},"set_column"," takes a column range, a width, and optionally a format — so one call does both jobs:",[212,928,930],{"className":247,"code":929,"language":249,"meta":217,"style":217},"import pandas as pd\nfrom xlsxwriter.utility import xl_col_to_name\n\ndef write_fitted(df, path, sheet_name=\"Report\", formats=None):\n    \"\"\"Write a DataFrame with fitted column widths and number formats.\"\"\"\n    formats = formats or {}\n    widths = fitted_widths(df, formats)\n\n    with pd.ExcelWriter(path, engine=\"xlsxwriter\") as writer:\n        df.to_excel(writer, sheet_name=sheet_name, index=False)\n        book, sheet = writer.book, writer.sheets[sheet_name]\n\n        header = book.add_format({\n            \"bold\": True, \"bg_color\": \"#EEF2FF\", \"border\": 1,\n            \"align\": \"center\", \"valign\": \"vcenter\",\n        })\n        cache = {}\n\n        for position, name in enumerate(df.columns):\n            sheet.write(0, position, str(name), header)\n\n            fmt_string = formats.get(name)\n            cell_format = None\n            if fmt_string:\n                # Reuse format objects — one per distinct format string.\n                cell_format = cache.setdefault(\n                    fmt_string, book.add_format({\"num_format\": fmt_string})\n                )\n\n            letter = xl_col_to_name(position)\n            sheet.set_column(f\"{letter}:{letter}\", widths[name], cell_format)\n\n        sheet.freeze_panes(1, 0)\n        sheet.autofilter(0, 0, len(df), len(df.columns) - 1)\n\n    return path\n\nwrite_fitted(df, \"report.xlsx\", formats=FORMATS)\n",[14,931,932,942,955,959,983,988,1002,1011,1015,1040,1063,1073,1077,1087,1119,1141,1146,1155,1159,1175,1191,1196,1207,1218,1227,1233,1244,1256,1262,1267,1278,1314,1319,1333,1365,1370,1378,1383],{"__ignoreMap":217},[221,933,934,936,938,940],{"class":223,"line":224},[221,935,257],{"class":256},[221,937,261],{"class":260},[221,939,264],{"class":256},[221,941,267],{"class":260},[221,943,944,947,950,952],{"class":223,"line":270},[221,945,946],{"class":256},"from",[221,948,949],{"class":260}," xlsxwriter.utility ",[221,951,257],{"class":256},[221,953,954],{"class":260}," xl_col_to_name\n",[221,956,957],{"class":223,"line":277},[221,958,274],{"emptyLinePlaceholder":273},[221,960,961,963,966,969,971,974,977,979,981],{"class":223,"line":289},[221,962,414],{"class":256},[221,964,965],{"class":417}," write_fitted",[221,967,968],{"class":260},"(df, path, sheet_name",[221,970,283],{"class":256},[221,972,973],{"class":231},"\"Report\"",[221,975,976],{"class":260},", formats",[221,978,283],{"class":256},[221,980,750],{"class":348},[221,982,445],{"class":260},[221,984,985],{"class":223,"line":315},[221,986,987],{"class":231},"    \"\"\"Write a DataFrame with fitted column widths and number formats.\"\"\"\n",[221,989,990,993,995,998,1000],{"class":223,"line":340},[221,991,992],{"class":260},"    formats ",[221,994,283],{"class":256},[221,996,997],{"class":260}," formats ",[221,999,792],{"class":256},[221,1001,460],{"class":260},[221,1003,1004,1006,1008],{"class":223,"line":364},[221,1005,455],{"class":260},[221,1007,283],{"class":256},[221,1009,1010],{"class":260}," fitted_widths(df, formats)\n",[221,1012,1013],{"class":223,"line":378},[221,1014,274],{"emptyLinePlaceholder":273},[221,1016,1017,1020,1023,1027,1029,1032,1035,1037],{"class":223,"line":386},[221,1018,1019],{"class":256},"    with",[221,1021,1022],{"class":260}," pd.ExcelWriter(path, ",[221,1024,1026],{"class":1025},"sa561","engine",[221,1028,283],{"class":256},[221,1030,1031],{"class":231},"\"xlsxwriter\"",[221,1033,1034],{"class":260},") ",[221,1036,264],{"class":256},[221,1038,1039],{"class":260}," writer:\n",[221,1041,1042,1045,1048,1050,1053,1056,1058,1061],{"class":223,"line":394},[221,1043,1044],{"class":260},"        df.to_excel(writer, ",[221,1046,1047],{"class":1025},"sheet_name",[221,1049,283],{"class":256},[221,1051,1052],{"class":260},"sheet_name, ",[221,1054,1055],{"class":1025},"index",[221,1057,283],{"class":256},[221,1059,1060],{"class":348},"False",[221,1062,845],{"class":260},[221,1064,1065,1068,1070],{"class":223,"line":577},[221,1066,1067],{"class":260},"        book, sheet ",[221,1069,283],{"class":256},[221,1071,1072],{"class":260}," writer.book, writer.sheets[sheet_name]\n",[221,1074,1075],{"class":223,"line":582},[221,1076,274],{"emptyLinePlaceholder":273},[221,1078,1079,1082,1084],{"class":223,"line":591},[221,1080,1081],{"class":260},"        header ",[221,1083,283],{"class":256},[221,1085,1086],{"class":260}," book.add_format({\n",[221,1088,1089,1092,1094,1097,1099,1102,1104,1107,1109,1112,1114,1117],{"class":223,"line":596},[221,1090,1091],{"class":231},"            \"bold\"",[221,1093,675],{"class":260},[221,1095,1096],{"class":348},"True",[221,1098,301],{"class":260},[221,1100,1101],{"class":231},"\"bg_color\"",[221,1103,675],{"class":260},[221,1105,1106],{"class":231},"\"#EEF2FF\"",[221,1108,301],{"class":260},[221,1110,1111],{"class":231},"\"border\"",[221,1113,675],{"class":260},[221,1115,1116],{"class":348},"1",[221,1118,375],{"class":260},[221,1120,1121,1124,1126,1129,1131,1134,1136,1139],{"class":223,"line":605},[221,1122,1123],{"class":231},"            \"align\"",[221,1125,675],{"class":260},[221,1127,1128],{"class":231},"\"center\"",[221,1130,301],{"class":260},[221,1132,1133],{"class":231},"\"valign\"",[221,1135,675],{"class":260},[221,1137,1138],{"class":231},"\"vcenter\"",[221,1140,375],{"class":260},[221,1142,1143],{"class":223,"line":854},[221,1144,1145],{"class":260},"        })\n",[221,1147,1148,1151,1153],{"class":223,"line":859},[221,1149,1150],{"class":260},"        cache ",[221,1152,283],{"class":256},[221,1154,460],{"class":260},[221,1156,1157],{"class":223,"line":890},[221,1158,274],{"emptyLinePlaceholder":273},[221,1160,1161,1164,1167,1169,1172],{"class":223,"line":903},[221,1162,1163],{"class":256},"        for",[221,1165,1166],{"class":260}," position, name ",[221,1168,471],{"class":256},[221,1170,1171],{"class":348}," enumerate",[221,1173,1174],{"class":260},"(df.columns):\n",[221,1176,1178,1181,1183,1186,1188],{"class":223,"line":1177},20,[221,1179,1180],{"class":260},"            sheet.write(",[221,1182,51],{"class":348},[221,1184,1185],{"class":260},", position, ",[221,1187,492],{"class":348},[221,1189,1190],{"class":260},"(name), header)\n",[221,1192,1194],{"class":223,"line":1193},21,[221,1195,274],{"emptyLinePlaceholder":273},[221,1197,1199,1202,1204],{"class":223,"line":1198},22,[221,1200,1201],{"class":260},"            fmt_string ",[221,1203,283],{"class":256},[221,1205,1206],{"class":260}," formats.get(name)\n",[221,1208,1210,1213,1215],{"class":223,"line":1209},23,[221,1211,1212],{"class":260},"            cell_format ",[221,1214,283],{"class":256},[221,1216,1217],{"class":348}," None\n",[221,1219,1221,1224],{"class":223,"line":1220},24,[221,1222,1223],{"class":256},"            if",[221,1225,1226],{"class":260}," fmt_string:\n",[221,1228,1230],{"class":223,"line":1229},25,[221,1231,1232],{"class":608},"                # Reuse format objects — one per distinct format string.\n",[221,1234,1236,1239,1241],{"class":223,"line":1235},26,[221,1237,1238],{"class":260},"                cell_format ",[221,1240,283],{"class":256},[221,1242,1243],{"class":260}," cache.setdefault(\n",[221,1245,1247,1250,1253],{"class":223,"line":1246},27,[221,1248,1249],{"class":260},"                    fmt_string, book.add_format({",[221,1251,1252],{"class":231},"\"num_format\"",[221,1254,1255],{"class":260},": fmt_string})\n",[221,1257,1259],{"class":223,"line":1258},28,[221,1260,1261],{"class":260},"                )\n",[221,1263,1265],{"class":223,"line":1264},29,[221,1266,274],{"emptyLinePlaceholder":273},[221,1268,1270,1273,1275],{"class":223,"line":1269},30,[221,1271,1272],{"class":260},"            letter ",[221,1274,283],{"class":256},[221,1276,1277],{"class":260}," xl_col_to_name(position)\n",[221,1279,1281,1284,1287,1290,1294,1297,1300,1303,1305,1307,1309,1311],{"class":223,"line":1280},31,[221,1282,1283],{"class":260},"            sheet.set_column(",[221,1285,1286],{"class":256},"f",[221,1288,1289],{"class":231},"\"",[221,1291,1293],{"class":1292},"sSjpA","{",[221,1295,1296],{"class":260},"letter",[221,1298,1299],{"class":1292},"}",[221,1301,1302],{"class":231},":",[221,1304,1293],{"class":1292},[221,1306,1296],{"class":260},[221,1308,1299],{"class":1292},[221,1310,1289],{"class":231},[221,1312,1313],{"class":260},", widths[name], cell_format)\n",[221,1315,1317],{"class":223,"line":1316},32,[221,1318,274],{"emptyLinePlaceholder":273},[221,1320,1322,1325,1327,1329,1331],{"class":223,"line":1321},33,[221,1323,1324],{"class":260},"        sheet.freeze_panes(",[221,1326,1116],{"class":348},[221,1328,301],{"class":260},[221,1330,51],{"class":348},[221,1332,845],{"class":260},[221,1334,1336,1339,1341,1343,1345,1347,1349,1352,1354,1357,1360,1363],{"class":223,"line":1335},34,[221,1337,1338],{"class":260},"        sheet.autofilter(",[221,1340,51],{"class":348},[221,1342,301],{"class":260},[221,1344,51],{"class":348},[221,1346,301],{"class":260},[221,1348,498],{"class":348},[221,1350,1351],{"class":260},"(df), ",[221,1353,498],{"class":348},[221,1355,1356],{"class":260},"(df.columns) ",[221,1358,1359],{"class":256},"-",[221,1361,1362],{"class":348}," 1",[221,1364,845],{"class":260},[221,1366,1368],{"class":223,"line":1367},35,[221,1369,274],{"emptyLinePlaceholder":273},[221,1371,1373,1375],{"class":223,"line":1372},36,[221,1374,585],{"class":256},[221,1376,1377],{"class":260}," path\n",[221,1379,1381],{"class":223,"line":1380},37,[221,1382,274],{"emptyLinePlaceholder":273},[221,1384,1386,1389,1392,1394,1397,1399,1401],{"class":223,"line":1385},38,[221,1387,1388],{"class":260},"write_fitted(df, ",[221,1390,1391],{"class":231},"\"report.xlsx\"",[221,1393,301],{"class":260},[221,1395,1396],{"class":1025},"formats",[221,1398,283],{"class":256},[221,1400,862],{"class":348},[221,1402,845],{"class":260},[10,1404,1405,1406,1409],{},"Caching the format objects matters. ",[14,1407,1408],{},"add_format"," creates a new entry every call, and creating one per column in a wide report inflates the workbook's format table for no benefit.",[10,1411,1412,1413,1416,1417,1419],{},"Recent xlsxwriter versions also offer ",[14,1414,1415],{},"sheet.autofit()",", which estimates widths from what has been written. It is a reasonable one-liner, but it works only from the data already on the sheet and does not know about number formats you apply afterwards — so it re-creates the ",[14,1418,20],{}," problem on date and currency columns. The explicit helper stays predictable.",[207,1421,1423],{"id":1422},"step-4-write-the-widths-with-openpyxl","Step 4 — Write the widths with openpyxl",[29,1425,39,1431,39,1434,39,1437,39,1440,39,1445,39,1451,39,1456,39,1461,39,1466,39,1469,39,1473,39,1477,39,1480,39,1483,39,1486,39,1490,39,1494,39,1497,39,1500,39,1504,39,1508,39,1512,39,1515],{"viewBox":1426,"role":32,"ariaLabel":1427,"ariaLabelledBy":1428,"xmlns":37,"style":38},"0 0 800 228","How a column width is chosen: take the longer of the header and the widest value, add padding, then clamp between a minimum and a maximum.",[1429,1430],"calc-t","calc-d",[41,1432,1433],{"id":1429},"The width calculation, one step at a time",[45,1435,1436],{"id":1430},"Four stages producing a final width. The header length and the longest value length are compared and the larger taken. A small padding is added so text does not touch the cell border. The result is then clamped between a minimum, so a column of single digits stays usable, and a maximum, so one long free-text value cannot make the column dominate the sheet. For a formatted column the format string's display width replaces the measured value entirely.",[49,1438],{"x":51,"y":51,"width":52,"height":1439,"fill":54},"228",[49,1441],{"x":712,"y":161,"width":1442,"height":105,"rx":193,"fill":1443,"stroke":1444,"style":75},"164","#ebebfd","var(--brand,#5b5cf0)",[56,1446,1450],{"x":1447,"y":1448,"style":1449},"96","56","font-size:10.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","1 · measure",[56,1452,1455],{"x":1447,"y":1453,"style":1454},"100","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","max(header, value)",[56,1457,1460],{"x":1447,"y":1458,"style":1459},"122","font-size:10px;fill:var(--muted,#5b6780);text-anchor:middle","in characters",[1462,1463],"polygon",{"points":1464,"fill":1465},"194,110 182,104 182,116","#5b5cf0",[49,1467],{"x":1468,"y":161,"width":1442,"height":105,"rx":193,"fill":194,"stroke":195,"style":75},"200",[56,1470,1472],{"x":1471,"y":1448,"style":1449},"282","2 · pad",[56,1474,1476],{"x":1471,"y":1453,"style":1475},"font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","+ 2",[56,1478,1479],{"x":1471,"y":1458,"style":1459},"clear of the border",[1462,1481],{"points":1482,"fill":1465},"380,110 368,104 368,116",[49,1484],{"x":1485,"y":161,"width":1442,"height":105,"rx":193,"fill":136,"stroke":137,"style":75},"386",[56,1487,1489],{"x":1488,"y":1448,"style":1449},"468","3 · clamp",[56,1491,1493],{"x":1488,"y":1453,"style":1492},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","min 8 · max 50",[56,1495,1496],{"x":1488,"y":1458,"style":1459},"usable, not dominant",[1462,1498],{"points":1499,"fill":1465},"566,110 554,104 554,116",[49,1501],{"x":1502,"y":161,"width":1503,"height":105,"rx":193,"fill":73,"stroke":74,"style":75},"572","212",[56,1505,1507],{"x":1506,"y":1448,"style":1449},"678","4 · override",[56,1509,1511],{"x":1506,"y":1453,"style":1510},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","formatted column?",[56,1513,1514],{"x":1506,"y":1458,"style":1459},"use the format's width",[56,1516,1519],{"x":198,"y":1517,"style":1518},"186","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","step 4 is what prevents ##### — the stored value 46249 is five characters, \"2026-08-15\" is ten",[10,1521,1522,1523,1302],{},"When you are modifying an existing workbook rather than creating one, the equivalent lives on ",[14,1524,1525],{},"column_dimensions",[212,1527,1529],{"className":247,"code":1528,"language":249,"meta":217,"style":217},"from openpyxl import load_workbook\nfrom openpyxl.utils import get_column_letter\n\ndef fit_existing(path, dest, sheet_name=None, sample=500,\n                 padding=2, min_width=8, max_width=50):\n    \"\"\"Fit the columns of a workbook that already exists.\"\"\"\n    wb = load_workbook(path)\n    ws = wb[sheet_name] if sheet_name else wb.active\n\n    for column in ws.iter_cols(min_row=1, max_row=min(sample, ws.max_row)):\n        longest = 0\n        for cell in column:\n            if cell.value is None:\n                continue\n            # Approximate the rendered width for formatted numbers.\n            text = (cell.number_format\n                    if cell.number_format not in (\"General\",) and\n                    isinstance(cell.value, (int, float))\n                    else str(cell.value))\n            longest = max(longest, len(text))\n\n        letter = get_column_letter(column[0].column)\n        ws.column_dimensions[letter].width = max(\n            min_width, min(max_width, longest + padding)\n        )\n\n    wb.save(dest)\n    return dest\n",[14,1530,1531,1543,1555,1559,1583,1606,1611,1621,1641,1645,1677,1686,1698,1714,1719,1724,1734,1760,1777,1788,1805,1809,1824,1835,1850,1854,1858,1863],{"__ignoreMap":217},[221,1532,1533,1535,1538,1540],{"class":223,"line":224},[221,1534,946],{"class":256},[221,1536,1537],{"class":260}," openpyxl ",[221,1539,257],{"class":256},[221,1541,1542],{"class":260}," load_workbook\n",[221,1544,1545,1547,1550,1552],{"class":223,"line":270},[221,1546,946],{"class":256},[221,1548,1549],{"class":260}," openpyxl.utils ",[221,1551,257],{"class":256},[221,1553,1554],{"class":260}," get_column_letter\n",[221,1556,1557],{"class":223,"line":277},[221,1558,274],{"emptyLinePlaceholder":273},[221,1560,1561,1563,1566,1569,1571,1573,1576,1578,1581],{"class":223,"line":289},[221,1562,414],{"class":256},[221,1564,1565],{"class":417}," fit_existing",[221,1567,1568],{"class":260},"(path, dest, sheet_name",[221,1570,283],{"class":256},[221,1572,750],{"class":348},[221,1574,1575],{"class":260},", sample",[221,1577,283],{"class":256},[221,1579,1580],{"class":348},"500",[221,1582,375],{"class":260},[221,1584,1585,1588,1590,1592,1594,1596,1598,1600,1602,1604],{"class":223,"line":315},[221,1586,1587],{"class":260},"                 padding",[221,1589,283],{"class":256},[221,1591,426],{"class":348},[221,1593,429],{"class":260},[221,1595,283],{"class":256},[221,1597,434],{"class":348},[221,1599,437],{"class":260},[221,1601,283],{"class":256},[221,1603,442],{"class":348},[221,1605,445],{"class":260},[221,1607,1608],{"class":223,"line":340},[221,1609,1610],{"class":231},"    \"\"\"Fit the columns of a workbook that already exists.\"\"\"\n",[221,1612,1613,1616,1618],{"class":223,"line":364},[221,1614,1615],{"class":260},"    wb ",[221,1617,283],{"class":256},[221,1619,1620],{"class":260}," load_workbook(path)\n",[221,1622,1623,1626,1628,1631,1633,1636,1638],{"class":223,"line":378},[221,1624,1625],{"class":260},"    ws ",[221,1627,283],{"class":256},[221,1629,1630],{"class":260}," wb[sheet_name] ",[221,1632,504],{"class":256},[221,1634,1635],{"class":260}," sheet_name ",[221,1637,513],{"class":256},[221,1639,1640],{"class":260}," wb.active\n",[221,1642,1643],{"class":223,"line":386},[221,1644,274],{"emptyLinePlaceholder":273},[221,1646,1647,1649,1652,1654,1657,1660,1662,1664,1666,1669,1671,1674],{"class":223,"line":394},[221,1648,465],{"class":256},[221,1650,1651],{"class":260}," column ",[221,1653,471],{"class":256},[221,1655,1656],{"class":260}," ws.iter_cols(",[221,1658,1659],{"class":1025},"min_row",[221,1661,283],{"class":256},[221,1663,1116],{"class":348},[221,1665,301],{"class":260},[221,1667,1668],{"class":1025},"max_row",[221,1670,283],{"class":256},[221,1672,1673],{"class":348},"min",[221,1675,1676],{"class":260},"(sample, ws.max_row)):\n",[221,1678,1679,1682,1684],{"class":223,"line":577},[221,1680,1681],{"class":260},"        longest ",[221,1683,283],{"class":256},[221,1685,516],{"class":348},[221,1687,1688,1690,1693,1695],{"class":223,"line":582},[221,1689,1163],{"class":256},[221,1691,1692],{"class":260}," cell ",[221,1694,471],{"class":256},[221,1696,1697],{"class":260}," column:\n",[221,1699,1700,1702,1705,1708,1711],{"class":223,"line":591},[221,1701,1223],{"class":256},[221,1703,1704],{"class":260}," cell.value ",[221,1706,1707],{"class":256},"is",[221,1709,1710],{"class":348}," None",[221,1712,1713],{"class":260},":\n",[221,1715,1716],{"class":223,"line":596},[221,1717,1718],{"class":256},"                continue\n",[221,1720,1721],{"class":223,"line":605},[221,1722,1723],{"class":608},"            # Approximate the rendered width for formatted numbers.\n",[221,1725,1726,1729,1731],{"class":223,"line":854},[221,1727,1728],{"class":260},"            text ",[221,1730,283],{"class":256},[221,1732,1733],{"class":260}," (cell.number_format\n",[221,1735,1736,1739,1742,1745,1748,1751,1754,1757],{"class":223,"line":859},[221,1737,1738],{"class":256},"                    if",[221,1740,1741],{"class":260}," cell.number_format ",[221,1743,1744],{"class":256},"not",[221,1746,1747],{"class":256}," in",[221,1749,1750],{"class":260}," (",[221,1752,1753],{"class":231},"\"General\"",[221,1755,1756],{"class":260},",) ",[221,1758,1759],{"class":256},"and\n",[221,1761,1762,1765,1768,1770,1772,1775],{"class":223,"line":890},[221,1763,1764],{"class":348},"                    isinstance",[221,1766,1767],{"class":260},"(cell.value, (",[221,1769,556],{"class":348},[221,1771,301],{"class":260},[221,1773,1774],{"class":348},"float",[221,1776,900],{"class":260},[221,1778,1779,1782,1785],{"class":223,"line":903},[221,1780,1781],{"class":256},"                    else",[221,1783,1784],{"class":348}," str",[221,1786,1787],{"class":260},"(cell.value))\n",[221,1789,1790,1793,1795,1797,1800,1802],{"class":223,"line":1177},[221,1791,1792],{"class":260},"            longest ",[221,1794,283],{"class":256},[221,1796,531],{"class":348},[221,1798,1799],{"class":260},"(longest, ",[221,1801,498],{"class":348},[221,1803,1804],{"class":260},"(text))\n",[221,1806,1807],{"class":223,"line":1193},[221,1808,274],{"emptyLinePlaceholder":273},[221,1810,1811,1814,1816,1819,1821],{"class":223,"line":1198},[221,1812,1813],{"class":260},"        letter ",[221,1815,283],{"class":256},[221,1817,1818],{"class":260}," get_column_letter(column[",[221,1820,51],{"class":348},[221,1822,1823],{"class":260},"].column)\n",[221,1825,1826,1829,1831,1833],{"class":223,"line":1209},[221,1827,1828],{"class":260},"        ws.column_dimensions[letter].width ",[221,1830,283],{"class":256},[221,1832,531],{"class":348},[221,1834,534],{"class":260},[221,1836,1837,1840,1842,1845,1847],{"class":223,"line":1220},[221,1838,1839],{"class":260},"            min_width, ",[221,1841,1673],{"class":348},[221,1843,1844],{"class":260},"(max_width, longest ",[221,1846,571],{"class":256},[221,1848,1849],{"class":260}," padding)\n",[221,1851,1852],{"class":223,"line":1229},[221,1853,521],{"class":260},[221,1855,1856],{"class":223,"line":1235},[221,1857,274],{"emptyLinePlaceholder":273},[221,1859,1860],{"class":223,"line":1246},[221,1861,1862],{"class":260},"    wb.save(dest)\n",[221,1864,1865,1867],{"class":223,"line":1258},[221,1866,585],{"class":256},[221,1868,1869],{"class":260}," dest\n",[10,1871,1872,1873,27],{},"Sampling the first few hundred rows rather than all of them is deliberate — on a large sheet, measuring every cell costs more than the resulting widths are worth, and the first few hundred rows are almost always representative. The related row and column sizing options are covered in ",[23,1874,1876],{"href":1875},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fstyling-excel-cells-with-openpyxl\u002Fset-column-width-and-row-height-in-openpyxl\u002F","setting column width and row height in openpyxl",[207,1878,1880],{"id":1879},"common-pitfalls-and-fixes","Common pitfalls and fixes",[1882,1883,1884,1900],"table",{},[1885,1886,1887],"thead",{},[1888,1889,1890,1894,1897],"tr",{},[1891,1892,1893],"th",{},"Symptom",[1891,1895,1896],{},"Cause",[1891,1898,1899],{},"Fix",[1901,1902,1903,1917,1931,1945,1961,1977,1990,2001],"tbody",{},[1888,1904,1905,1911,1914],{},[1906,1907,1908,1909],"td",{},"Dates show as ",[14,1910,20],{},[1906,1912,1913],{},"Column narrower than the formatted date",[1906,1915,1916],{},"Size from the format string, not the value.",[1888,1918,1919,1922,1925],{},[1906,1920,1921],{},"One column fills the screen",[1906,1923,1924],{},"Free text, no cap",[1906,1926,1927,1928,1930],{},"Set a ",[14,1929,619],{}," of around 50.",[1888,1932,1933,1936,1939],{},[1906,1934,1935],{},"Header clipped although values fit",[1906,1937,1938],{},"Header longer than any value",[1906,1940,1941,1942,1944],{},"Include ",[14,1943,623],{}," in the measurement.",[1888,1946,1947,1950,1958],{},[1906,1948,1949],{},"Widths ignored",[1906,1951,1952,1954,1955],{},[14,1953,925],{}," called before ",[14,1956,1957],{},"to_excel",[1906,1959,1960],{},"Write the frame first, then format.",[1888,1962,1963,1971,1974],{},[1906,1964,1965,1968,1969],{},[14,1966,1967],{},"AttributeError"," on ",[14,1970,1525],{},[1906,1972,1973],{},"Used the xlsxwriter API on an openpyxl sheet",[1906,1975,1976],{},"The two engines have different APIs.",[1888,1978,1979,1982,1987],{},[1906,1980,1981],{},"Workbook has thousands of formats",[1906,1983,1984,1986],{},[14,1985,1408],{}," called per column",[1906,1988,1989],{},"Cache format objects by format string.",[1888,1991,1992,1995,1998],{},[1906,1993,1994],{},"Widths wrong after appending rows",[1906,1996,1997],{},"Fitted before the new rows were written",[1906,1999,2000],{},"Re-fit after the final write.",[1888,2002,2003,2006,2009],{},[1906,2004,2005],{},"Very slow on a large sheet",[1906,2007,2008],{},"Measuring every cell",[1906,2010,2011],{},"Sample the first few hundred rows.",[207,2013,2015],{"id":2014},"performance-and-scale-notes","Performance and scale notes",[29,2017,39,2023,39,2026,39,2029,39,2032,39,2036,39,2041,39,2047,39,2052,39,2056,39,2060,39,2062,39,2066,39,2069,39,2071,39,2076,39,2079],{"viewBox":2018,"role":32,"ariaLabel":2019,"ariaLabelledBy":2020,"xmlns":37,"style":38},"0 0 800 220","Measurement cost against write cost: measuring scales with row count while applying a width is constant, so sampling the first thousand rows removes almost all the expense.",[2021,2022],"wcost-t","wcost-d",[41,2024,2025],{"id":2021},"Measuring scales with rows; applying does not",[45,2027,2028],{"id":2022},"Two cost bars for a million-row frame. Measuring every cell builds a string per cell across every column, which dominates the total. Sampling the first thousand rows reduces that to a sliver while giving practically the same widths. Applying the width itself, through set_column or column_dimensions, is a single operation per column and is unaffected by row count either way.",[49,2030],{"x":51,"y":51,"width":52,"height":2031,"fill":54},"220",[56,2033,2035],{"x":198,"y":59,"style":2034},"font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","fitting a 1,000,000-row frame with 30 columns",[49,2037],{"x":712,"y":1448,"width":2038,"height":59,"rx":2039,"fill":2040,"stroke":107},"152","6","#f0f2f5",[56,2042,2046],{"x":2043,"y":2044,"style":2045},"90","75","font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","measure everything",[49,2048],{"x":2049,"y":1448,"width":2050,"height":59,"rx":2051,"fill":73,"stroke":74,"style":75},"180","530","5",[56,2053,2055],{"x":2054,"y":105,"style":1510},"445","30,000,000 temporary strings",[49,2057],{"x":2058,"y":1448,"width":712,"height":59,"rx":2059,"fill":1465},"712","3",[49,2061],{"x":712,"y":84,"width":2038,"height":59,"rx":2039,"fill":2040,"stroke":107},[56,2063,2065],{"x":2043,"y":2064,"style":2045},"131","sample 1,000 rows",[49,2067],{"x":2049,"y":84,"width":69,"height":59,"rx":2051,"fill":2068},"#0f9488",[49,2070],{"x":1503,"y":84,"width":712,"height":59,"rx":2059,"fill":1465},[56,2072,2075],{"x":2073,"y":129,"style":2074},"240","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157)","practically the same widths",[49,2077],{"x":2049,"y":2078,"width":712,"height":712,"rx":2059,"fill":1465},"168",[56,2080,2084],{"x":2081,"y":2082,"style":2083},"202","179","font-size:11px;fill:var(--muted,#5b6780)","applying the width — constant, one call per column",[10,2086,2087,2088,2091],{},"Measuring is the cost, and it scales with rows. ",[14,2089,2090],{},"df[name].astype(str).map(len).max()"," materialises a string for every cell — on a million-row frame with thirty columns that is thirty million temporary strings.",[10,2093,2094],{},"Sample instead. Column widths are a presentation detail, and the widest value in the first thousand rows is almost always wide enough:",[212,2096,2098],{"className":247,"code":2097,"language":249,"meta":217,"style":217},"def column_widths_sampled(df, sample=1000, **kwargs):\n    \"\"\"Measure a sample rather than the whole frame.\"\"\"\n    head = df.head(sample) if len(df) > sample else df\n    return column_widths(head, **kwargs)\n",[14,2099,2100,2121,2126,2153],{"__ignoreMap":217},[221,2101,2102,2104,2107,2110,2112,2115,2117,2119],{"class":223,"line":224},[221,2103,414],{"class":256},[221,2105,2106],{"class":417}," column_widths_sampled",[221,2108,2109],{"class":260},"(df, sample",[221,2111,283],{"class":256},[221,2113,2114],{"class":348},"1000",[221,2116,301],{"class":260},[221,2118,755],{"class":256},[221,2120,758],{"class":260},[221,2122,2123],{"class":223,"line":270},[221,2124,2125],{"class":231},"    \"\"\"Measure a sample rather than the whole frame.\"\"\"\n",[221,2127,2128,2131,2133,2136,2138,2140,2142,2145,2148,2150],{"class":223,"line":277},[221,2129,2130],{"class":260},"    head ",[221,2132,283],{"class":256},[221,2134,2135],{"class":260}," df.head(sample) ",[221,2137,504],{"class":256},[221,2139,507],{"class":348},[221,2141,510],{"class":260},[221,2143,2144],{"class":256},">",[221,2146,2147],{"class":260}," sample ",[221,2149,513],{"class":256},[221,2151,2152],{"class":260}," df\n",[221,2154,2155,2157,2160,2162],{"class":223,"line":289},[221,2156,585],{"class":256},[221,2158,2159],{"class":260}," column_widths(head, ",[221,2161,755],{"class":256},[221,2163,777],{"class":260},[10,2165,2166,2167,2171,2172,675,2175,2177,2178,2180],{},"Two further habits. ",[2168,2169,2170],"strong",{},"Skip measurement entirely for formatted columns"," — the format string gives the width with no data access at all, which is free regardless of row count. And ",[2168,2173,2174],{},"apply widths per column, not per cell",[14,2176,925],{}," and ",[14,2179,1525],{}," are both O(1) in the number of rows, so the write side never scales badly even when the measurement does.",[10,2182,2183,2184,2187,2188,2190],{},"For genuinely large reports written through openpyxl's streaming mode, note that ",[14,2185,2186],{},"write_only"," workbooks do support ",[14,2189,1525],{},", but you must set them before appending rows — the dimensions are written into the sheet header, which is emitted first. That makes format-derived widths the only practical option there, since you cannot measure data you have not written yet:",[212,2192,2194],{"className":247,"code":2193,"language":249,"meta":217,"style":217},"from openpyxl import Workbook\nfrom openpyxl.utils import get_column_letter\nfrom openpyxl.worksheet.dimensions import ColumnDimension\n\nwb = Workbook(write_only=True)\nws = wb.create_sheet(\"Report\")\n\nfor position, width in enumerate([25, 14, 12, 50], start=1):\n    letter = get_column_letter(position)\n    ws.column_dimensions[letter] = ColumnDimension(ws, index=letter, width=width)\n\nws.append(list(df.columns))\nfor row in df.itertuples(index=False):\n    ws.append(list(row))\nwb.save(\"large_report.xlsx\")\n",[14,2195,2196,2207,2217,2229,2233,2251,2265,2269,2311,2321,2346,2350,2361,2381,2391],{"__ignoreMap":217},[221,2197,2198,2200,2202,2204],{"class":223,"line":224},[221,2199,946],{"class":256},[221,2201,1537],{"class":260},[221,2203,257],{"class":256},[221,2205,2206],{"class":260}," Workbook\n",[221,2208,2209,2211,2213,2215],{"class":223,"line":270},[221,2210,946],{"class":256},[221,2212,1549],{"class":260},[221,2214,257],{"class":256},[221,2216,1554],{"class":260},[221,2218,2219,2221,2224,2226],{"class":223,"line":277},[221,2220,946],{"class":256},[221,2222,2223],{"class":260}," openpyxl.worksheet.dimensions ",[221,2225,257],{"class":256},[221,2227,2228],{"class":260}," ColumnDimension\n",[221,2230,2231],{"class":223,"line":289},[221,2232,274],{"emptyLinePlaceholder":273},[221,2234,2235,2238,2240,2243,2245,2247,2249],{"class":223,"line":315},[221,2236,2237],{"class":260},"wb ",[221,2239,283],{"class":256},[221,2241,2242],{"class":260}," Workbook(",[221,2244,2186],{"class":1025},[221,2246,283],{"class":256},[221,2248,1096],{"class":348},[221,2250,845],{"class":260},[221,2252,2253,2256,2258,2261,2263],{"class":223,"line":340},[221,2254,2255],{"class":260},"ws ",[221,2257,283],{"class":256},[221,2259,2260],{"class":260}," wb.create_sheet(",[221,2262,973],{"class":231},[221,2264,845],{"class":260},[221,2266,2267],{"class":223,"line":364},[221,2268,274],{"emptyLinePlaceholder":273},[221,2270,2271,2274,2277,2279,2281,2284,2287,2289,2291,2293,2295,2297,2299,2302,2305,2307,2309],{"class":223,"line":378},[221,2272,2273],{"class":256},"for",[221,2275,2276],{"class":260}," position, width ",[221,2278,471],{"class":256},[221,2280,1171],{"class":348},[221,2282,2283],{"class":260},"([",[221,2285,2286],{"class":348},"25",[221,2288,301],{"class":260},[221,2290,712],{"class":348},[221,2292,301],{"class":260},[221,2294,193],{"class":348},[221,2296,301],{"class":260},[221,2298,442],{"class":348},[221,2300,2301],{"class":260},"], ",[221,2303,2304],{"class":1025},"start",[221,2306,283],{"class":256},[221,2308,1116],{"class":348},[221,2310,445],{"class":260},[221,2312,2313,2316,2318],{"class":223,"line":386},[221,2314,2315],{"class":260},"    letter ",[221,2317,283],{"class":256},[221,2319,2320],{"class":260}," get_column_letter(position)\n",[221,2322,2323,2326,2328,2331,2333,2335,2338,2341,2343],{"class":223,"line":394},[221,2324,2325],{"class":260},"    ws.column_dimensions[letter] ",[221,2327,283],{"class":256},[221,2329,2330],{"class":260}," ColumnDimension(ws, ",[221,2332,1055],{"class":1025},[221,2334,283],{"class":256},[221,2336,2337],{"class":260},"letter, ",[221,2339,2340],{"class":1025},"width",[221,2342,283],{"class":256},[221,2344,2345],{"class":260},"width)\n",[221,2347,2348],{"class":223,"line":577},[221,2349,274],{"emptyLinePlaceholder":273},[221,2351,2352,2355,2358],{"class":223,"line":582},[221,2353,2354],{"class":260},"ws.append(",[221,2356,2357],{"class":348},"list",[221,2359,2360],{"class":260},"(df.columns))\n",[221,2362,2363,2365,2368,2370,2373,2375,2377,2379],{"class":223,"line":591},[221,2364,2273],{"class":256},[221,2366,2367],{"class":260}," row ",[221,2369,471],{"class":256},[221,2371,2372],{"class":260}," df.itertuples(",[221,2374,1055],{"class":1025},[221,2376,283],{"class":256},[221,2378,1060],{"class":348},[221,2380,445],{"class":260},[221,2382,2383,2386,2388],{"class":223,"line":596},[221,2384,2385],{"class":260},"    ws.append(",[221,2387,2357],{"class":348},[221,2389,2390],{"class":260},"(row))\n",[221,2392,2393,2396,2399],{"class":223,"line":605},[221,2394,2395],{"class":260},"wb.save(",[221,2397,2398],{"class":231},"\"large_report.xlsx\"",[221,2400,845],{"class":260},[10,2402,2403,2404,2408],{},"That combination — streaming rows with widths set up front — is what keeps a ",[23,2405,2407],{"href":2406},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fwrite-large-dataframes-to-excel-with-write-only-mode\u002F","large write-only report"," both memory-flat and readable.",[207,2410,2412],{"id":2411},"conclusion","Conclusion",[10,2414,2415,2416,2418,2419,2421,2422,2424],{},"pandas has no auto-fit, so measure and set the widths yourself. Take the longest of the header and the values, add a little padding, and clamp between a sensible minimum and a maximum of around fifty so one free-text column cannot dominate. For any column carrying a number format, size it from the format string instead of the underlying value — that is what eliminates ",[14,2417,20],{}," on dates and currency. Then write the width with ",[14,2420,925],{}," in xlsxwriter or ",[14,2423,1525],{}," in openpyxl, sample rather than measure everything on large frames, and always fit after the final write.",[207,2426,2428],{"id":2427},"frequently-asked-questions","Frequently asked questions",[10,2430,2431,2437],{},[2168,2432,2433,2434,2436],{},"Does pandas have an autofit option for ",[14,2435,1957],{},"?","\nNo. pandas writes values and leaves every column at Excel's default width, so wide text is clipped and formatted numbers show as hash marks. You measure the content and set the widths yourself.",[10,2439,2440,2443],{},[2168,2441,2442],{},"What unit is the width argument in?","\nApproximately the number of characters of the default font. It is not pixels and not points, which is why measuring the longest string's length and adding a small padding works well as an estimate.",[10,2445,2446,2451,2452,2455,2456,2458],{},[2168,2447,2448,2449,2436],{},"Why is my date column still showing ",[14,2450,20],{},"\nBecause you measured the underlying value rather than its formatted display. A date stored as ",[14,2453,2454],{},"46249"," is five characters, but rendered as ",[14,2457,16],{}," it needs ten. Size date and currency columns from the format string.",[10,2460,2461,2464,2465,2468],{},[2168,2462,2463],{},"Does xlsxwriter have a real autofit?","\nRecent versions expose a worksheet ",[14,2466,2467],{},"autofit"," method that estimates widths from the written data. It is convenient, but it works from what has been written so far and does not know your intended number formats, so an explicit helper is still more predictable.",[10,2470,2471,2474],{},[2168,2472,2473],{},"Should I cap the maximum width?","\nYes. One long free-text comment can otherwise make a column hundreds of characters wide and push everything else off screen. Cap at around fifty and let the cell wrap or clip.",[207,2476,2478],{"id":2477},"related","Related",[2480,2481,2482,2489,2495,2504,2511],"ul",{},[2483,2484,2485,2486,2488],"li",{},"Up to the parent: ",[23,2487,26],{"href":25}," — the writing pass this formats.",[2483,2490,2491,2494],{},[23,2492,2493],{"href":1875},"Set Column Width and Row Height in openpyxl"," — the openpyxl dimension API in depth.",[2483,2496,2497,2500,2501,2503],{},[23,2498,2499],{"href":915},"Fix Excel Serial Numbers Showing Instead of Dates"," — the other half of the ",[14,2502,20],{}," problem.",[2483,2505,2506,2510],{},[23,2507,2509],{"href":2508},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","Write a Formatted Excel Report with xlsxwriter"," — the full formatting pass around these widths.",[2483,2512,2513,2516],{},[23,2514,2515],{"href":2406},"Write Large DataFrames to Excel with Write-Only Mode"," — setting widths before streaming rows.",[2518,2519,2520],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":217,"searchDepth":270,"depth":270,"links":2522},[2523,2524,2525,2526,2527,2528,2529,2530,2531,2532],{"id":209,"depth":270,"text":210},{"id":400,"depth":270,"text":401},{"id":647,"depth":270,"text":648},{"id":919,"depth":270,"text":920},{"id":1422,"depth":270,"text":1423},{"id":1879,"depth":270,"text":1880},{"id":2014,"depth":270,"text":2015},{"id":2411,"depth":270,"text":2412},{"id":2427,"depth":270,"text":2428},{"id":2477,"depth":270,"text":2478},"Stop Excel reports opening with ##### and truncated headers — measure content width in Python and set column widths with xlsxwriter or openpyxl, including a reusable helper.","md",[2536,2539,2541,2544,2546],{"q":2537,"a":2538},"Does pandas have an autofit option for to_excel?","No. pandas writes values and leaves every column at Excel's default width, so wide text is clipped and formatted numbers show as hash marks. You measure the content and set the widths yourself.",{"q":2442,"a":2540},"Approximately the number of characters of the default font. It is not pixels and not points, which is why measuring the longest string's length and adding a small padding works well as an estimate.",{"q":2542,"a":2543},"Why is my date column still showing","Because you measured the underlying value rather than its formatted display. A date stored as 46249 is five characters, but rendered as yyyy-mm-dd it needs ten. Size date and currency columns from the format string.",{"q":2463,"a":2545},"Recent versions expose a worksheet autofit method that estimates widths from the written data. It is convenient, but it works from what has been written so far and does not know your intended number formats, so an explicit helper is still more predictable.",{"q":2473,"a":2547},"Yes. One long free-text comment can otherwise make a column hundreds of characters wide and push everything else off screen. Cap at around fifty and let the cell wrap or clip.",{},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas",{"title":2551,"description":2552},"Auto-Fit Excel Column Widths from pandas (Python)","Excel has no auto-fit through pandas, so measure the content yourself: a width helper for xlsxwriter set_column and openpyxl column_dimensions, with caps and format-aware sizing.","auto-fit-column-widths-when-writing-with-pandas","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas\u002Findex","how-to","K6wiwBYRaxi1xDwcvJxSC3qznR865_XroXqsl03BKHo",[2558,2562],{"title":2559,"path":2560,"stem":2561,"children":-1},"Append a DataFrame to an Existing Excel File with pandas","\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fappend-a-dataframe-to-an-existing-excel-file-with-pandas","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fappend-a-dataframe-to-an-existing-excel-file-with-pandas\u002Findex",{"title":2563,"path":2564,"stem":2565,"children":-1},"openpyxl vs xlsxwriter vs pandas.ExcelWriter","\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter\u002Findex",1786800029162]