[{"data":1,"prerenderedAt":3300},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value":3291},{"id":4,"title":5,"body":6,"dateModified":3268,"datePublished":3268,"description":3269,"extension":3270,"faq":3271,"meta":3282,"navigation":240,"path":3283,"seo":3284,"slug":3287,"stem":3288,"type":3289,"__hash__":3290},"docs\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value\u002Findex.md","Split One Excel Sheet into Multiple Files by Column Value",{"type":7,"value":8,"toc":3255},"minimark",[9,31,173,178,209,212,459,463,468,604,617,621,704,707,1166,1177,1180,1339,1343,1346,1760,1766,1770,1773,1885,2231,2238,2242,2245,2565,2575,2579,2692,2696,2701,2713,3004,3007,3010,3136,3143,3147,3156,3160,3166,3179,3198,3204,3210,3214,3251],[10,11,12,13,17,18,21,22,24,25,30],"p",{},"Somebody sends a single export with every region in it, and four people each want only their own rows. Or the finance system produces one file a month and each cost centre needs its own copy. Splitting is a three-line ",[14,15,16],"code",{},"groupby"," and a ",[14,19,20],{},"to_excel"," — and then the practical work begins: filenames that are legal on every platform, blank values that ",[14,23,16],{}," silently drops, and formatting that has to be identical on every output. This guide covers the split properly. It extends ",[26,27,29],"a",{"href":28},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002F","Working with Multiple Excel Sheets in Python",".",[32,33,42,43,42,47,42,51,42,58,42,68,42,75,42,80,42,85,42,98,42,108,42,115,42,121,42,127,42,132,42,135,42,139,42,142,42,146,42,151,42,156,42,159,42,163,42,165,42,168,42,170],"svg",{"viewBox":34,"role":35,"ariaLabel":36,"ariaLabelledBy":37,"xmlns":40,"style":41},"0 0 800 254","img","One combined sheet splitting two ways: into one workbook per region for separate distribution, or into one sheet per region inside a single workbook.",[38,39],"split-t","split-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  ",[44,45,46],"title",{"id":38},"Two shapes for the same split",[48,49,50],"desc",{"id":39},"A combined sheet containing rows for North, South and West splits two ways. The upper path produces three separate workbooks, one per region, suitable for sending each region only its own data. The lower path produces one workbook with three sheets, suitable for a single reader who wants everything organised by region. The choice follows from who receives the output.",[52,53],"rect",{"x":54,"y":54,"width":55,"height":56,"fill":57},"0","800","254","#ffffff",[52,59],{"x":60,"y":61,"width":62,"height":63,"rx":64,"fill":65,"stroke":66,"style":67},"14","94","164","80","13","#ebebfd","var(--brand,#5b5cf0)","stroke-width:2px",[69,70,74],"text",{"x":71,"y":72,"style":73},"96","78","font-size:11px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","one combined export",[69,76,79],{"x":71,"y":77,"style":78},"126","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","all_regions.xlsx",[69,81,84],{"x":71,"y":82,"style":83},"148","font-size:10.5px;fill:var(--muted,#5b6780);text-anchor:middle","North, South, West",[86,87,90,91,90,95,42],"g",{"stroke":88,"style":67,"fill":89},"var(--line,#cdd5e6)","none","\n    ",[92,93],"path",{"d":94},"M178 118 H 212 V 62 H 246",[92,96],{"d":97},"M178 150 H 212 V 194 H 246",[86,99,90,101,90,105,42],{"fill":100},"#5b5cf0",[102,103],"polygon",{"points":104},"254,62 242,56 242,68",[102,106],{"points":107},"254,194 242,188 242,200",[52,109],{"x":110,"y":111,"width":112,"height":72,"rx":64,"fill":113,"stroke":114,"style":67},"262","24","522","#d9f4f1","var(--teal,#0f9488)",[69,116,120],{"x":117,"y":118,"style":119},"288","48","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157)","one workbook per region",[52,122],{"x":117,"y":123,"width":124,"height":125,"rx":126,"fill":57,"stroke":114},"58","146","30","6",[69,128,131],{"x":129,"y":72,"style":130},"361","font-size:10.5px;fill:var(--text,#172033);text-anchor:middle","north.xlsx",[52,133],{"x":134,"y":123,"width":124,"height":125,"rx":126,"fill":57,"stroke":114},"444",[69,136,138],{"x":137,"y":72,"style":130},"517","south.xlsx",[52,140],{"x":141,"y":123,"width":124,"height":125,"rx":126,"fill":57,"stroke":114},"600",[69,143,145],{"x":144,"y":72,"style":130},"673","west.xlsx",[52,147],{"x":110,"y":148,"width":112,"height":72,"rx":64,"fill":149,"stroke":150,"style":67},"156","#fdefd8","var(--gold,#b4740a)",[69,152,155],{"x":117,"y":153,"style":154},"180","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06)","one workbook, one sheet per region",[52,157],{"x":117,"y":158,"width":124,"height":125,"rx":126,"fill":57,"stroke":150},"190",[69,160,162],{"x":129,"y":161,"style":130},"210","sheet: North",[52,164],{"x":134,"y":158,"width":124,"height":125,"rx":126,"fill":57,"stroke":150},[69,166,167],{"x":137,"y":161,"style":130},"sheet: South",[52,169],{"x":141,"y":158,"width":124,"height":125,"rx":126,"fill":57,"stroke":150},[69,171,172],{"x":144,"y":161,"style":130},"sheet: West",[174,175,177],"h2",{"id":176},"prerequisites","Prerequisites",[179,180,185],"pre",{"className":181,"code":182,"language":183,"meta":184,"style":184},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl xlsxwriter\n","bash","",[14,186,187],{"__ignoreMap":184},[188,189,192,196,200,203,206],"span",{"class":190,"line":191},"line",1,[188,193,195],{"class":194},"sMTad","pip",[188,197,199],{"class":198},"srMev"," install",[188,201,202],{"class":198}," pandas",[188,204,205],{"class":198}," openpyxl",[188,207,208],{"class":198}," xlsxwriter\n",[10,210,211],{},"A combined export to split:",[179,213,217],{"className":214,"code":215,"language":216,"meta":184,"style":184},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\npd.DataFrame({\n    \"region\": [\"North\", \"South\", \"West\", \"North\", \"South\", None],\n    \"branch\": [f\"Branch {i}\" for i in range(1, 7)],\n    \"revenue\": [5150.00, 4268.50, 3511.25, 2980.10, 3140.75, 1820.00],\n    \"invoice_date\": pd.to_datetime(\n        [\"2026-08-01\", \"2026-08-03\", \"2026-08-05\",\n         \"2026-08-09\", \"2026-08-14\", \"2026-08-15\"]\n    ),\n}).to_excel(\"all_regions.xlsx\", index=False)\n","python",[14,218,219,235,242,248,288,341,379,388,410,429,435],{"__ignoreMap":184},[188,220,221,225,229,232],{"class":190,"line":191},[188,222,224],{"class":223},"s-kum","import",[188,226,228],{"class":227},"skGVy"," pandas ",[188,230,231],{"class":223},"as",[188,233,234],{"class":227}," pd\n",[188,236,238],{"class":190,"line":237},2,[188,239,241],{"emptyLinePlaceholder":240},true,"\n",[188,243,245],{"class":190,"line":244},3,[188,246,247],{"class":227},"pd.DataFrame({\n",[188,249,251,254,257,260,263,266,268,271,273,275,277,279,281,285],{"class":190,"line":250},4,[188,252,253],{"class":198},"    \"region\"",[188,255,256],{"class":227},": [",[188,258,259],{"class":198},"\"North\"",[188,261,262],{"class":227},", ",[188,264,265],{"class":198},"\"South\"",[188,267,262],{"class":227},[188,269,270],{"class":198},"\"West\"",[188,272,262],{"class":227},[188,274,259],{"class":198},[188,276,262],{"class":227},[188,278,265],{"class":198},[188,280,262],{"class":227},[188,282,284],{"class":283},"sP0c6","None",[188,286,287],{"class":227},"],\n",[188,289,291,294,296,299,302,306,309,312,315,318,321,324,327,330,333,335,338],{"class":190,"line":290},5,[188,292,293],{"class":198},"    \"branch\"",[188,295,256],{"class":227},[188,297,298],{"class":223},"f",[188,300,301],{"class":198},"\"Branch ",[188,303,305],{"class":304},"sSjpA","{",[188,307,308],{"class":227},"i",[188,310,311],{"class":304},"}",[188,313,314],{"class":198},"\"",[188,316,317],{"class":223}," for",[188,319,320],{"class":227}," i ",[188,322,323],{"class":223},"in",[188,325,326],{"class":283}," range",[188,328,329],{"class":227},"(",[188,331,332],{"class":283},"1",[188,334,262],{"class":227},[188,336,337],{"class":283},"7",[188,339,340],{"class":227},")],\n",[188,342,344,347,349,352,354,357,359,362,364,367,369,372,374,377],{"class":190,"line":343},6,[188,345,346],{"class":198},"    \"revenue\"",[188,348,256],{"class":227},[188,350,351],{"class":283},"5150.00",[188,353,262],{"class":227},[188,355,356],{"class":283},"4268.50",[188,358,262],{"class":227},[188,360,361],{"class":283},"3511.25",[188,363,262],{"class":227},[188,365,366],{"class":283},"2980.10",[188,368,262],{"class":227},[188,370,371],{"class":283},"3140.75",[188,373,262],{"class":227},[188,375,376],{"class":283},"1820.00",[188,378,287],{"class":227},[188,380,382,385],{"class":190,"line":381},7,[188,383,384],{"class":198},"    \"invoice_date\"",[188,386,387],{"class":227},": pd.to_datetime(\n",[188,389,391,394,397,399,402,404,407],{"class":190,"line":390},8,[188,392,393],{"class":227},"        [",[188,395,396],{"class":198},"\"2026-08-01\"",[188,398,262],{"class":227},[188,400,401],{"class":198},"\"2026-08-03\"",[188,403,262],{"class":227},[188,405,406],{"class":198},"\"2026-08-05\"",[188,408,409],{"class":227},",\n",[188,411,413,416,418,421,423,426],{"class":190,"line":412},9,[188,414,415],{"class":198},"         \"2026-08-09\"",[188,417,262],{"class":227},[188,419,420],{"class":198},"\"2026-08-14\"",[188,422,262],{"class":227},[188,424,425],{"class":198},"\"2026-08-15\"",[188,427,428],{"class":227},"]\n",[188,430,432],{"class":190,"line":431},10,[188,433,434],{"class":227},"    ),\n",[188,436,438,441,444,446,450,453,456],{"class":190,"line":437},11,[188,439,440],{"class":227},"}).to_excel(",[188,442,443],{"class":198},"\"all_regions.xlsx\"",[188,445,262],{"class":227},[188,447,449],{"class":448},"sa561","index",[188,451,452],{"class":223},"=",[188,454,455],{"class":283},"False",[188,457,458],{"class":227},")\n",[174,460,462],{"id":461},"step-1-split-into-one-file-per-value","Step 1 — Split into one file per value",[10,464,465,467],{},[14,466,16],{}," gives you the key and the rows together:",[179,469,471],{"className":214,"code":470,"language":216,"meta":184,"style":184},"from pathlib import Path\nimport pandas as pd\n\ndf = pd.read_excel(\"all_regions.xlsx\")\n\nout = Path(\"split\")\nout.mkdir(exist_ok=True)\n\nfor region, group in df.groupby(\"region\"):\n    group.to_excel(out \u002F f\"{region}.xlsx\", index=False)\n",[14,472,473,486,496,500,514,518,533,548,552,571],{"__ignoreMap":184},[188,474,475,478,481,483],{"class":190,"line":191},[188,476,477],{"class":223},"from",[188,479,480],{"class":227}," pathlib ",[188,482,224],{"class":223},[188,484,485],{"class":227}," Path\n",[188,487,488,490,492,494],{"class":190,"line":237},[188,489,224],{"class":223},[188,491,228],{"class":227},[188,493,231],{"class":223},[188,495,234],{"class":227},[188,497,498],{"class":190,"line":244},[188,499,241],{"emptyLinePlaceholder":240},[188,501,502,505,507,510,512],{"class":190,"line":250},[188,503,504],{"class":227},"df ",[188,506,452],{"class":223},[188,508,509],{"class":227}," pd.read_excel(",[188,511,443],{"class":198},[188,513,458],{"class":227},[188,515,516],{"class":190,"line":290},[188,517,241],{"emptyLinePlaceholder":240},[188,519,520,523,525,528,531],{"class":190,"line":343},[188,521,522],{"class":227},"out ",[188,524,452],{"class":223},[188,526,527],{"class":227}," Path(",[188,529,530],{"class":198},"\"split\"",[188,532,458],{"class":227},[188,534,535,538,541,543,546],{"class":190,"line":381},[188,536,537],{"class":227},"out.mkdir(",[188,539,540],{"class":448},"exist_ok",[188,542,452],{"class":223},[188,544,545],{"class":283},"True",[188,547,458],{"class":227},[188,549,550],{"class":190,"line":390},[188,551,241],{"emptyLinePlaceholder":240},[188,553,554,557,560,562,565,568],{"class":190,"line":412},[188,555,556],{"class":223},"for",[188,558,559],{"class":227}," region, group ",[188,561,323],{"class":223},[188,563,564],{"class":227}," df.groupby(",[188,566,567],{"class":198},"\"region\"",[188,569,570],{"class":227},"):\n",[188,572,573,576,579,582,584,586,589,591,594,596,598,600,602],{"class":190,"line":431},[188,574,575],{"class":227},"    group.to_excel(out ",[188,577,578],{"class":223},"\u002F",[188,580,581],{"class":223}," f",[188,583,314],{"class":198},[188,585,305],{"class":304},[188,587,588],{"class":227},"region",[188,590,311],{"class":304},[188,592,593],{"class":198},".xlsx\"",[188,595,262],{"class":227},[188,597,449],{"class":448},[188,599,452],{"class":223},[188,601,455],{"class":283},[188,603,458],{"class":227},[10,605,606,607,609,610,612,613,616],{},"That works on clean data and fails on real data in two ways. The row whose region is ",[14,608,284],{}," vanished — ",[14,611,16],{}," drops missing keys by default — and a region named ",[14,614,615],{},"North\u002FSouth"," would raise, because a slash is not legal in a filename.",[174,618,620],{"id":619},"step-2-make-the-filenames-safe","Step 2 — Make the filenames safe",[32,622,42,628,42,631,42,634,42,637,42,642,42,645,42,650,42,655,42,659,42,664,42,669,42,674,42,676,42,679,42,682,42,687,42,689,42,692,42,695,42,699,42,701],{"viewBox":623,"role":35,"ariaLabel":624,"ariaLabelledBy":625,"xmlns":40,"style":41},"0 0 800 236","Filename sanitising: illegal characters become underscores, whitespace collapses, reserved device names gain a suffix, and collisions created by sanitising are resolved with a counter.",[626,627],"fn-t","fn-d",[44,629,630],{"id":626},"Four group values and the filenames they become",[48,632,633],{"id":627},"Four rows pairing a raw group value with its sanitised filename. A value containing a forward slash has it replaced by an underscore. A value that is missing entirely falls back to a fixed name. A value equal to a Windows reserved device name gains a trailing underscore so the file can be created. And a second value that sanitises to a name already used gains a numeric suffix, because sanitising itself can create collisions that did not exist in the source.",[52,635],{"x":54,"y":54,"width":55,"height":636,"fill":57},"236",[69,638,641],{"x":639,"y":125,"style":640},"220","font-size:12px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","group value",[69,643,644],{"x":141,"y":125,"style":640},"filename",[52,646],{"x":125,"y":647,"width":648,"height":649,"rx":126,"fill":65,"stroke":66,"style":67},"44","380","34",[69,651,654],{"x":639,"y":652,"style":653},"66","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","\"North\u002FSouth\"",[52,656],{"x":657,"y":647,"width":658,"height":649,"rx":126,"fill":113,"stroke":114,"style":67},"436","334",[69,660,663],{"x":661,"y":652,"style":662},"603","font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","North_South.xlsx",[52,665],{"x":125,"y":666,"width":648,"height":649,"rx":126,"fill":667,"stroke":668,"style":67},"86","#fee8f2","var(--accent,#f43f8f)",[69,670,673],{"x":639,"y":671,"style":672},"108","font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","None (missing key)",[52,675],{"x":657,"y":666,"width":658,"height":649,"rx":126,"fill":113,"stroke":114,"style":67},[69,677,678],{"x":661,"y":671,"style":662},"unspecified.xlsx",[52,680],{"x":125,"y":681,"width":648,"height":649,"rx":126,"fill":149,"stroke":150,"style":67},"128",[69,683,686],{"x":639,"y":684,"style":685},"150","font-size:11px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","\"CON\"",[52,688],{"x":657,"y":681,"width":658,"height":649,"rx":126,"fill":113,"stroke":114,"style":67},[69,690,691],{"x":661,"y":684,"style":662},"CON_.xlsx — reserved on Windows",[52,693],{"x":125,"y":694,"width":648,"height":649,"rx":126,"fill":65,"stroke":66,"style":67},"170",[69,696,698],{"x":639,"y":697,"style":653},"192","\"North-South\"",[52,700],{"x":657,"y":694,"width":658,"height":649,"rx":126,"fill":149,"stroke":150,"style":67},[69,702,703],{"x":661,"y":697,"style":685},"North_South_2.xlsx — collision",[10,705,706],{},"Sanitising is more than removing slashes. Windows also forbids a set of reserved device names, disallows trailing dots and spaces, and caps path length:",[179,708,710],{"className":214,"code":709,"language":216,"meta":184,"style":184},"import re\n\nILLEGAL = re.compile(r'[\u003C>:\"\u002F\\\\|?*\\x00-\\x1f]')\nRESERVED = {\n    \"CON\", \"PRN\", \"AUX\", \"NUL\",\n    *(f\"COM{i}\" for i in range(1, 10)),\n    *(f\"LPT{i}\" for i in range(1, 10)),\n}\n\ndef safe_filename(value, fallback=\"unspecified\", max_length=80):\n    \"\"\"Turn an arbitrary group key into a filename that works everywhere.\"\"\"\n    text = fallback if value is None or (isinstance(value, float) and value != value) \\\n        else str(value)\n\n    text = ILLEGAL.sub(\"_\", text)\n    text = re.sub(r\"\\s+\", \" \", text).strip(\" .\")       # no trailing dots or spaces\n    text = text[:max_length].strip() or fallback\n\n    if text.upper() in RESERVED:\n        text = f\"{text}_\"\n    return text\n\nprint(safe_filename(\"North\u002FSouth\"))     # North_South\nprint(safe_filename(None))              # unspecified\nprint(safe_filename(\"CON\"))             # CON_\n",[14,711,712,719,723,769,779,801,841,878,883,887,913,918,970,982,987,1006,1046,1062,1067,1084,1105,1114,1119,1136,1151],{"__ignoreMap":184},[188,713,714,716],{"class":190,"line":191},[188,715,224],{"class":223},[188,717,718],{"class":227}," re\n",[188,720,721],{"class":190,"line":237},[188,722,241],{"emptyLinePlaceholder":240},[188,724,725,728,731,734,737,740,743,746,750,753,756,759,762,765,767],{"class":190,"line":244},[188,726,727],{"class":283},"ILLEGAL",[188,729,730],{"class":223}," =",[188,732,733],{"class":227}," re.compile(",[188,735,736],{"class":223},"r",[188,738,739],{"class":198},"'",[188,741,742],{"class":283},"[",[188,744,745],{"class":304},"\u003C>:\"\u002F",[188,747,749],{"class":748},"s_b0D","\\\\",[188,751,752],{"class":304},"|?*",[188,754,755],{"class":748},"\\x00",[188,757,758],{"class":304},"-",[188,760,761],{"class":748},"\\x1f",[188,763,764],{"class":283},"]",[188,766,739],{"class":198},[188,768,458],{"class":227},[188,770,771,774,776],{"class":190,"line":250},[188,772,773],{"class":283},"RESERVED",[188,775,730],{"class":223},[188,777,778],{"class":227}," {\n",[188,780,781,784,786,789,791,794,796,799],{"class":190,"line":290},[188,782,783],{"class":198},"    \"CON\"",[188,785,262],{"class":227},[188,787,788],{"class":198},"\"PRN\"",[188,790,262],{"class":227},[188,792,793],{"class":198},"\"AUX\"",[188,795,262],{"class":227},[188,797,798],{"class":198},"\"NUL\"",[188,800,409],{"class":227},[188,802,803,806,808,810,813,815,817,819,821,823,825,827,829,831,833,835,838],{"class":190,"line":343},[188,804,805],{"class":223},"    *",[188,807,329],{"class":227},[188,809,298],{"class":223},[188,811,812],{"class":198},"\"COM",[188,814,305],{"class":304},[188,816,308],{"class":227},[188,818,311],{"class":304},[188,820,314],{"class":198},[188,822,317],{"class":223},[188,824,320],{"class":227},[188,826,323],{"class":223},[188,828,326],{"class":283},[188,830,329],{"class":227},[188,832,332],{"class":283},[188,834,262],{"class":227},[188,836,837],{"class":283},"10",[188,839,840],{"class":227},")),\n",[188,842,843,845,847,849,852,854,856,858,860,862,864,866,868,870,872,874,876],{"class":190,"line":381},[188,844,805],{"class":223},[188,846,329],{"class":227},[188,848,298],{"class":223},[188,850,851],{"class":198},"\"LPT",[188,853,305],{"class":304},[188,855,308],{"class":227},[188,857,311],{"class":304},[188,859,314],{"class":198},[188,861,317],{"class":223},[188,863,320],{"class":227},[188,865,323],{"class":223},[188,867,326],{"class":283},[188,869,329],{"class":227},[188,871,332],{"class":283},[188,873,262],{"class":227},[188,875,837],{"class":283},[188,877,840],{"class":227},[188,879,880],{"class":190,"line":390},[188,881,882],{"class":227},"}\n",[188,884,885],{"class":190,"line":412},[188,886,241],{"emptyLinePlaceholder":240},[188,888,889,892,896,899,901,904,907,909,911],{"class":190,"line":431},[188,890,891],{"class":223},"def",[188,893,895],{"class":894},"s_Opv"," safe_filename",[188,897,898],{"class":227},"(value, fallback",[188,900,452],{"class":223},[188,902,903],{"class":198},"\"unspecified\"",[188,905,906],{"class":227},", max_length",[188,908,452],{"class":223},[188,910,63],{"class":283},[188,912,570],{"class":227},[188,914,915],{"class":190,"line":437},[188,916,917],{"class":198},"    \"\"\"Turn an arbitrary group key into a filename that works everywhere.\"\"\"\n",[188,919,921,924,926,929,932,935,938,941,944,947,950,953,956,959,962,964,967],{"class":190,"line":920},12,[188,922,923],{"class":227},"    text ",[188,925,452],{"class":223},[188,927,928],{"class":227}," fallback ",[188,930,931],{"class":223},"if",[188,933,934],{"class":227}," value ",[188,936,937],{"class":223},"is",[188,939,940],{"class":283}," None",[188,942,943],{"class":223}," or",[188,945,946],{"class":227}," (",[188,948,949],{"class":283},"isinstance",[188,951,952],{"class":227},"(value, ",[188,954,955],{"class":283},"float",[188,957,958],{"class":227},") ",[188,960,961],{"class":223},"and",[188,963,934],{"class":227},[188,965,966],{"class":223},"!=",[188,968,969],{"class":227}," value) \\\n",[188,971,973,976,979],{"class":190,"line":972},13,[188,974,975],{"class":223},"        else",[188,977,978],{"class":283}," str",[188,980,981],{"class":227},"(value)\n",[188,983,985],{"class":190,"line":984},14,[188,986,241],{"emptyLinePlaceholder":240},[188,988,990,992,994,997,1000,1003],{"class":190,"line":989},15,[188,991,923],{"class":227},[188,993,452],{"class":223},[188,995,996],{"class":283}," ILLEGAL",[188,998,999],{"class":227},".sub(",[188,1001,1002],{"class":198},"\"_\"",[188,1004,1005],{"class":227},", text)\n",[188,1007,1009,1011,1013,1016,1018,1020,1023,1026,1028,1030,1033,1036,1039,1042],{"class":190,"line":1008},16,[188,1010,923],{"class":227},[188,1012,452],{"class":223},[188,1014,1015],{"class":227}," re.sub(",[188,1017,736],{"class":223},[188,1019,314],{"class":198},[188,1021,1022],{"class":283},"\\s",[188,1024,1025],{"class":223},"+",[188,1027,314],{"class":198},[188,1029,262],{"class":227},[188,1031,1032],{"class":198},"\" \"",[188,1034,1035],{"class":227},", text).strip(",[188,1037,1038],{"class":198},"\" .\"",[188,1040,1041],{"class":227},")       ",[188,1043,1045],{"class":1044},"s-wDw","# no trailing dots or spaces\n",[188,1047,1049,1051,1053,1056,1059],{"class":190,"line":1048},17,[188,1050,923],{"class":227},[188,1052,452],{"class":223},[188,1054,1055],{"class":227}," text[:max_length].strip() ",[188,1057,1058],{"class":223},"or",[188,1060,1061],{"class":227}," fallback\n",[188,1063,1065],{"class":190,"line":1064},18,[188,1066,241],{"emptyLinePlaceholder":240},[188,1068,1070,1073,1076,1078,1081],{"class":190,"line":1069},19,[188,1071,1072],{"class":223},"    if",[188,1074,1075],{"class":227}," text.upper() ",[188,1077,323],{"class":223},[188,1079,1080],{"class":283}," RESERVED",[188,1082,1083],{"class":227},":\n",[188,1085,1087,1090,1092,1094,1096,1098,1100,1102],{"class":190,"line":1086},20,[188,1088,1089],{"class":227},"        text ",[188,1091,452],{"class":223},[188,1093,581],{"class":223},[188,1095,314],{"class":198},[188,1097,305],{"class":304},[188,1099,69],{"class":227},[188,1101,311],{"class":304},[188,1103,1104],{"class":198},"_\"\n",[188,1106,1108,1111],{"class":190,"line":1107},21,[188,1109,1110],{"class":223},"    return",[188,1112,1113],{"class":227}," text\n",[188,1115,1117],{"class":190,"line":1116},22,[188,1118,241],{"emptyLinePlaceholder":240},[188,1120,1122,1125,1128,1130,1133],{"class":190,"line":1121},23,[188,1123,1124],{"class":283},"print",[188,1126,1127],{"class":227},"(safe_filename(",[188,1129,654],{"class":198},[188,1131,1132],{"class":227},"))     ",[188,1134,1135],{"class":1044},"# North_South\n",[188,1137,1139,1141,1143,1145,1148],{"class":190,"line":1138},24,[188,1140,1124],{"class":283},[188,1142,1127],{"class":227},[188,1144,284],{"class":283},[188,1146,1147],{"class":227},"))              ",[188,1149,1150],{"class":1044},"# unspecified\n",[188,1152,1154,1156,1158,1160,1163],{"class":190,"line":1153},25,[188,1155,1124],{"class":283},[188,1157,1127],{"class":227},[188,1159,686],{"class":198},[188,1161,1162],{"class":227},"))             ",[188,1164,1165],{"class":1044},"# CON_\n",[10,1167,1168,1169,1172,1173,1176],{},"The ",[14,1170,1171],{},"value != value"," test is the idiomatic check for ",[14,1174,1175],{},"NaN",", which is the form a missing key takes when the column is numeric.",[10,1178,1179],{},"Sanitising creates collisions, so resolve them after the fact rather than hoping:",[179,1181,1183],{"className":214,"code":1182,"language":216,"meta":184,"style":184},"def unique_names(values, **kwargs):\n    \"\"\"Map each group key to a distinct, safe filename stem.\"\"\"\n    used, mapping = {}, {}\n    for value in values:\n        stem = safe_filename(value, **kwargs)\n        key = stem.lower()\n        if key in used:\n            used[key] += 1\n            stem = f\"{stem}_{used[key]}\"\n        else:\n            used[key] = 1\n        mapping[value] = stem\n    return mapping\n",[14,1184,1185,1201,1206,1216,1228,1243,1253,1266,1277,1308,1314,1322,1332],{"__ignoreMap":184},[188,1186,1187,1189,1192,1195,1198],{"class":190,"line":191},[188,1188,891],{"class":223},[188,1190,1191],{"class":894}," unique_names",[188,1193,1194],{"class":227},"(values, ",[188,1196,1197],{"class":223},"**",[188,1199,1200],{"class":227},"kwargs):\n",[188,1202,1203],{"class":190,"line":237},[188,1204,1205],{"class":198},"    \"\"\"Map each group key to a distinct, safe filename stem.\"\"\"\n",[188,1207,1208,1211,1213],{"class":190,"line":244},[188,1209,1210],{"class":227},"    used, mapping ",[188,1212,452],{"class":223},[188,1214,1215],{"class":227}," {}, {}\n",[188,1217,1218,1221,1223,1225],{"class":190,"line":250},[188,1219,1220],{"class":223},"    for",[188,1222,934],{"class":227},[188,1224,323],{"class":223},[188,1226,1227],{"class":227}," values:\n",[188,1229,1230,1233,1235,1238,1240],{"class":190,"line":290},[188,1231,1232],{"class":227},"        stem ",[188,1234,452],{"class":223},[188,1236,1237],{"class":227}," safe_filename(value, ",[188,1239,1197],{"class":223},[188,1241,1242],{"class":227},"kwargs)\n",[188,1244,1245,1248,1250],{"class":190,"line":343},[188,1246,1247],{"class":227},"        key ",[188,1249,452],{"class":223},[188,1251,1252],{"class":227}," stem.lower()\n",[188,1254,1255,1258,1261,1263],{"class":190,"line":381},[188,1256,1257],{"class":223},"        if",[188,1259,1260],{"class":227}," key ",[188,1262,323],{"class":223},[188,1264,1265],{"class":227}," used:\n",[188,1267,1268,1271,1274],{"class":190,"line":390},[188,1269,1270],{"class":227},"            used[key] ",[188,1272,1273],{"class":223},"+=",[188,1275,1276],{"class":283}," 1\n",[188,1278,1279,1282,1284,1286,1288,1290,1293,1295,1298,1300,1303,1305],{"class":190,"line":412},[188,1280,1281],{"class":227},"            stem ",[188,1283,452],{"class":223},[188,1285,581],{"class":223},[188,1287,314],{"class":198},[188,1289,305],{"class":304},[188,1291,1292],{"class":227},"stem",[188,1294,311],{"class":304},[188,1296,1297],{"class":198},"_",[188,1299,305],{"class":304},[188,1301,1302],{"class":227},"used[key]",[188,1304,311],{"class":304},[188,1306,1307],{"class":198},"\"\n",[188,1309,1310,1312],{"class":190,"line":431},[188,1311,975],{"class":223},[188,1313,1083],{"class":227},[188,1315,1316,1318,1320],{"class":190,"line":437},[188,1317,1270],{"class":227},[188,1319,452],{"class":223},[188,1321,1276],{"class":283},[188,1323,1324,1327,1329],{"class":190,"line":920},[188,1325,1326],{"class":227},"        mapping[value] ",[188,1328,452],{"class":223},[188,1330,1331],{"class":227}," stem\n",[188,1333,1334,1336],{"class":190,"line":972},[188,1335,1110],{"class":223},[188,1337,1338],{"class":227}," mapping\n",[174,1340,1342],{"id":1341},"step-3-handle-the-missing-group-and-guard-the-count","Step 3 — Handle the missing group and guard the count",[10,1344,1345],{},"Two guards turn a fragile loop into something safe to run unattended:",[179,1347,1349],{"className":214,"code":1348,"language":216,"meta":184,"style":184},"from pathlib import Path\nimport pandas as pd\n\ndef split_to_files(df, key, out_dir=\"split\", max_groups=200, formatter=None):\n    \"\"\"Write one workbook per distinct value of `key`.\"\"\"\n    # dropna=False keeps the rows whose key is missing.\n    groups = list(df.groupby(key, dropna=False))\n\n    if len(groups) > max_groups:\n        raise ValueError(\n            f\"{len(groups)} distinct values in {key!r} — refusing to write that \"\n            f\"many files. Raise max_groups deliberately if this is intended.\"\n        )\n\n    out = Path(out_dir)\n    out.mkdir(parents=True, exist_ok=True)\n\n    names = unique_names([value for value, _ in groups])\n    written = []\n\n    for value, group in groups:\n        path = out \u002F f\"{names[value]}.xlsx\"\n        if formatter:\n            formatter(group, path)\n        else:\n            group.to_excel(path, index=False)\n        written.append((value, path, len(group)))\n\n    return written\n\nfor value, path, rows in split_to_files(pd.read_excel(\"all_regions.xlsx\"), \"region\"):\n    print(f\"{str(value):\u003C14} {rows:>4} rows -> {path.name}\")\n",[14,1350,1351,1361,1371,1375,1406,1411,1416,1439,1443,1459,1470,1503,1510,1515,1519,1529,1551,1555,1575,1585,1589,1601,1627,1634,1639,1645,1659,1670,1675,1683,1688,1710],{"__ignoreMap":184},[188,1352,1353,1355,1357,1359],{"class":190,"line":191},[188,1354,477],{"class":223},[188,1356,480],{"class":227},[188,1358,224],{"class":223},[188,1360,485],{"class":227},[188,1362,1363,1365,1367,1369],{"class":190,"line":237},[188,1364,224],{"class":223},[188,1366,228],{"class":227},[188,1368,231],{"class":223},[188,1370,234],{"class":227},[188,1372,1373],{"class":190,"line":244},[188,1374,241],{"emptyLinePlaceholder":240},[188,1376,1377,1379,1382,1385,1387,1389,1392,1394,1397,1400,1402,1404],{"class":190,"line":250},[188,1378,891],{"class":223},[188,1380,1381],{"class":894}," split_to_files",[188,1383,1384],{"class":227},"(df, key, out_dir",[188,1386,452],{"class":223},[188,1388,530],{"class":198},[188,1390,1391],{"class":227},", max_groups",[188,1393,452],{"class":223},[188,1395,1396],{"class":283},"200",[188,1398,1399],{"class":227},", formatter",[188,1401,452],{"class":223},[188,1403,284],{"class":283},[188,1405,570],{"class":227},[188,1407,1408],{"class":190,"line":290},[188,1409,1410],{"class":198},"    \"\"\"Write one workbook per distinct value of `key`.\"\"\"\n",[188,1412,1413],{"class":190,"line":343},[188,1414,1415],{"class":1044},"    # dropna=False keeps the rows whose key is missing.\n",[188,1417,1418,1421,1423,1426,1429,1432,1434,1436],{"class":190,"line":381},[188,1419,1420],{"class":227},"    groups ",[188,1422,452],{"class":223},[188,1424,1425],{"class":283}," list",[188,1427,1428],{"class":227},"(df.groupby(key, ",[188,1430,1431],{"class":448},"dropna",[188,1433,452],{"class":223},[188,1435,455],{"class":283},[188,1437,1438],{"class":227},"))\n",[188,1440,1441],{"class":190,"line":390},[188,1442,241],{"emptyLinePlaceholder":240},[188,1444,1445,1447,1450,1453,1456],{"class":190,"line":412},[188,1446,1072],{"class":223},[188,1448,1449],{"class":283}," len",[188,1451,1452],{"class":227},"(groups) ",[188,1454,1455],{"class":223},">",[188,1457,1458],{"class":227}," max_groups:\n",[188,1460,1461,1464,1467],{"class":190,"line":431},[188,1462,1463],{"class":223},"        raise",[188,1465,1466],{"class":283}," ValueError",[188,1468,1469],{"class":227},"(\n",[188,1471,1472,1475,1477,1479,1482,1485,1487,1490,1492,1495,1498,1500],{"class":190,"line":437},[188,1473,1474],{"class":223},"            f",[188,1476,314],{"class":198},[188,1478,305],{"class":304},[188,1480,1481],{"class":283},"len",[188,1483,1484],{"class":227},"(groups)",[188,1486,311],{"class":304},[188,1488,1489],{"class":198}," distinct values in ",[188,1491,305],{"class":304},[188,1493,1494],{"class":227},"key",[188,1496,1497],{"class":223},"!r",[188,1499,311],{"class":304},[188,1501,1502],{"class":198}," — refusing to write that \"\n",[188,1504,1505,1507],{"class":190,"line":920},[188,1506,1474],{"class":223},[188,1508,1509],{"class":198},"\"many files. Raise max_groups deliberately if this is intended.\"\n",[188,1511,1512],{"class":190,"line":972},[188,1513,1514],{"class":227},"        )\n",[188,1516,1517],{"class":190,"line":984},[188,1518,241],{"emptyLinePlaceholder":240},[188,1520,1521,1524,1526],{"class":190,"line":989},[188,1522,1523],{"class":227},"    out ",[188,1525,452],{"class":223},[188,1527,1528],{"class":227}," Path(out_dir)\n",[188,1530,1531,1534,1537,1539,1541,1543,1545,1547,1549],{"class":190,"line":1008},[188,1532,1533],{"class":227},"    out.mkdir(",[188,1535,1536],{"class":448},"parents",[188,1538,452],{"class":223},[188,1540,545],{"class":283},[188,1542,262],{"class":227},[188,1544,540],{"class":448},[188,1546,452],{"class":223},[188,1548,545],{"class":283},[188,1550,458],{"class":227},[188,1552,1553],{"class":190,"line":1048},[188,1554,241],{"emptyLinePlaceholder":240},[188,1556,1557,1560,1562,1565,1567,1570,1572],{"class":190,"line":1064},[188,1558,1559],{"class":227},"    names ",[188,1561,452],{"class":223},[188,1563,1564],{"class":227}," unique_names([value ",[188,1566,556],{"class":223},[188,1568,1569],{"class":227}," value, _ ",[188,1571,323],{"class":223},[188,1573,1574],{"class":227}," groups])\n",[188,1576,1577,1580,1582],{"class":190,"line":1069},[188,1578,1579],{"class":227},"    written ",[188,1581,452],{"class":223},[188,1583,1584],{"class":227}," []\n",[188,1586,1587],{"class":190,"line":1086},[188,1588,241],{"emptyLinePlaceholder":240},[188,1590,1591,1593,1596,1598],{"class":190,"line":1107},[188,1592,1220],{"class":223},[188,1594,1595],{"class":227}," value, group ",[188,1597,323],{"class":223},[188,1599,1600],{"class":227}," groups:\n",[188,1602,1603,1606,1608,1611,1613,1615,1617,1619,1622,1624],{"class":190,"line":1116},[188,1604,1605],{"class":227},"        path ",[188,1607,452],{"class":223},[188,1609,1610],{"class":227}," out ",[188,1612,578],{"class":223},[188,1614,581],{"class":223},[188,1616,314],{"class":198},[188,1618,305],{"class":304},[188,1620,1621],{"class":227},"names[value]",[188,1623,311],{"class":304},[188,1625,1626],{"class":198},".xlsx\"\n",[188,1628,1629,1631],{"class":190,"line":1121},[188,1630,1257],{"class":223},[188,1632,1633],{"class":227}," formatter:\n",[188,1635,1636],{"class":190,"line":1138},[188,1637,1638],{"class":227},"            formatter(group, path)\n",[188,1640,1641,1643],{"class":190,"line":1153},[188,1642,975],{"class":223},[188,1644,1083],{"class":227},[188,1646,1648,1651,1653,1655,1657],{"class":190,"line":1647},26,[188,1649,1650],{"class":227},"            group.to_excel(path, ",[188,1652,449],{"class":448},[188,1654,452],{"class":223},[188,1656,455],{"class":283},[188,1658,458],{"class":227},[188,1660,1662,1665,1667],{"class":190,"line":1661},27,[188,1663,1664],{"class":227},"        written.append((value, path, ",[188,1666,1481],{"class":283},[188,1668,1669],{"class":227},"(group)))\n",[188,1671,1673],{"class":190,"line":1672},28,[188,1674,241],{"emptyLinePlaceholder":240},[188,1676,1678,1680],{"class":190,"line":1677},29,[188,1679,1110],{"class":223},[188,1681,1682],{"class":227}," written\n",[188,1684,1686],{"class":190,"line":1685},30,[188,1687,241],{"emptyLinePlaceholder":240},[188,1689,1691,1693,1696,1698,1701,1703,1706,1708],{"class":190,"line":1690},31,[188,1692,556],{"class":223},[188,1694,1695],{"class":227}," value, path, rows ",[188,1697,323],{"class":223},[188,1699,1700],{"class":227}," split_to_files(pd.read_excel(",[188,1702,443],{"class":198},[188,1704,1705],{"class":227},"), ",[188,1707,567],{"class":198},[188,1709,570],{"class":227},[188,1711,1713,1716,1718,1720,1722,1724,1727,1730,1733,1735,1738,1741,1744,1746,1749,1751,1754,1756,1758],{"class":190,"line":1712},32,[188,1714,1715],{"class":283},"    print",[188,1717,329],{"class":227},[188,1719,298],{"class":223},[188,1721,314],{"class":198},[188,1723,305],{"class":304},[188,1725,1726],{"class":283},"str",[188,1728,1729],{"class":227},"(value)",[188,1731,1732],{"class":223},":\u003C14",[188,1734,311],{"class":304},[188,1736,1737],{"class":304}," {",[188,1739,1740],{"class":227},"rows",[188,1742,1743],{"class":223},":>4",[188,1745,311],{"class":304},[188,1747,1748],{"class":198}," rows -> ",[188,1750,305],{"class":304},[188,1752,1753],{"class":227},"path.name",[188,1755,311],{"class":304},[188,1757,314],{"class":198},[188,1759,458],{"class":227},[10,1761,1168,1762,1765],{},[14,1763,1764],{},"max_groups"," guard is the one that saves you. Splitting on a column you assumed had five values and actually has four thousand fills a directory and, if the next step emails each file, sends four thousand emails. Failing loudly is much better.",[174,1767,1769],{"id":1768},"step-4-format-every-output-identically","Step 4 — Format every output identically",[10,1771,1772],{},"If the outputs go to people, they should look like reports rather than raw dumps. Pass a formatter so every file is produced the same way:",[32,1774,42,1780,42,1783,42,1786,42,1790,42,1794,42,1799,42,1802,42,1806,42,1809,42,1812,42,1823,42,1826,42,1830,42,1836,42,1840,42,1844,42,1855,42,1867,42,1871,42,1875,42,1877,42,1880,42,1882],{"viewBox":1775,"role":35,"ariaLabel":1776,"ariaLabelledBy":1777,"xmlns":40,"style":41},"0 26 800 166","One formatter function applied to every split output, so all files share identical headers, widths, number formats and freeze panes.",[1778,1779],"fmtall-t","fmtall-d",[44,1781,1782],{"id":1778},"One formatter, applied to every split file",[48,1784,1785],{"id":1779},"Three grouped frames each pass through the same formatting function before being written. Because the formatting lives in one place, every output file carries the same styled header, the same column widths, the same currency and date formats, and the same frozen header row. Changing the look of all of them later means editing one function rather than a loop body that has drifted.",[52,1787],{"x":54,"y":1788,"width":55,"height":1789,"fill":57},"26","166",[52,1791],{"x":1792,"y":1793,"width":684,"height":649,"rx":337,"fill":65,"stroke":66,"style":67},"16","42",[69,1795,1798],{"x":1796,"y":1797,"style":653},"91","64","North rows",[52,1800],{"x":1792,"y":1801,"width":684,"height":649,"rx":337,"fill":65,"stroke":66,"style":67},"92",[69,1803,1805],{"x":1796,"y":1804,"style":653},"114","South rows",[52,1807],{"x":1792,"y":1808,"width":684,"height":649,"rx":337,"fill":65,"stroke":66,"style":67},"142",[69,1810,1811],{"x":1796,"y":62,"style":653},"West rows",[86,1813,90,1814,90,1817,90,1820,42],{"stroke":88,"style":67,"fill":89},[92,1815],{"d":1816},"M166 59 H 206 V 109",[92,1818],{"d":1819},"M166 109 H 210",[92,1821],{"d":1822},"M166 159 H 206 V 109",[102,1824],{"points":1825,"fill":100},"218,109 206,103 206,115",[52,1827],{"x":1828,"y":1829,"width":1396,"height":72,"rx":64,"fill":149,"stroke":150,"style":67},"226","70",[69,1831,1835],{"x":1832,"y":1833,"style":1834},"326","98","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","one formatter",[69,1837,1839],{"x":1832,"y":1838,"style":130},"120","header, widths, formats,",[69,1841,1843],{"x":1832,"y":1842,"style":130},"138","freeze panes, autofilter",[86,1845,90,1846,90,1849,90,1852,42],{"stroke":150,"style":67,"fill":89},[92,1847],{"d":1848},"M426 109 H 466 V 59 H 500",[92,1850],{"d":1851},"M426 109 H 470",[92,1853],{"d":1854},"M426 109 H 466 V 159 H 500",[86,1856,90,1858,90,1861,90,1864,42],{"fill":1857},"#b4740a",[102,1859],{"points":1860},"508,59 496,53 496,65",[102,1862],{"points":1863},"508,109 496,103 496,115",[102,1865],{"points":1866},"508,159 496,153 496,165",[52,1868],{"x":1869,"y":1793,"width":1870,"height":649,"rx":337,"fill":113,"stroke":114,"style":67},"516","268",[69,1872,1874],{"x":1873,"y":1797,"style":662},"650","north.xlsx — formatted",[52,1876],{"x":1869,"y":1801,"width":1870,"height":649,"rx":337,"fill":113,"stroke":114,"style":67},[69,1878,1879],{"x":1873,"y":1804,"style":662},"south.xlsx — identically",[52,1881],{"x":1869,"y":1808,"width":1870,"height":649,"rx":337,"fill":113,"stroke":114,"style":67},[69,1883,1884],{"x":1873,"y":62,"style":662},"west.xlsx — identically",[179,1886,1888],{"className":214,"code":1887,"language":216,"meta":184,"style":184},"import pandas as pd\n\ndef write_report(group, path, sheet_name=\"Detail\"):\n    \"\"\"Write one group as a formatted, self-contained report.\"\"\"\n    with pd.ExcelWriter(path, engine=\"xlsxwriter\",\n                        datetime_format=\"yyyy-mm-dd\") as writer:\n        group.to_excel(writer, sheet_name=sheet_name, index=False)\n\n        book, sheet = writer.book, writer.sheets[sheet_name]\n        header = book.add_format({\"bold\": True, \"bg_color\": \"#EEF2FF\",\n                                  \"border\": 1, \"align\": \"center\"})\n        money = book.add_format({\"num_format\": \"#,##0.00\"})\n\n        for position, name in enumerate(group.columns):\n            sheet.write(0, position, str(name), header)\n\n        sheet.set_column(\"A:A\", 16)\n        sheet.set_column(\"B:B\", 18)\n        sheet.set_column(\"C:C\", 14, money)\n        sheet.set_column(\"D:D\", 14)\n        sheet.freeze_panes(1, 0)\n        sheet.autofilter(0, 0, len(group), len(group.columns) - 1)\n\nsplit_to_files(pd.read_excel(\"all_regions.xlsx\"), \"region\",\n               formatter=write_report)\n",[14,1889,1890,1900,1904,1921,1926,1944,1961,1982,1986,1996,2026,2048,2067,2071,2087,2102,2106,2120,2134,2148,2161,2174,2204,2208,2221],{"__ignoreMap":184},[188,1891,1892,1894,1896,1898],{"class":190,"line":191},[188,1893,224],{"class":223},[188,1895,228],{"class":227},[188,1897,231],{"class":223},[188,1899,234],{"class":227},[188,1901,1902],{"class":190,"line":237},[188,1903,241],{"emptyLinePlaceholder":240},[188,1905,1906,1908,1911,1914,1916,1919],{"class":190,"line":244},[188,1907,891],{"class":223},[188,1909,1910],{"class":894}," write_report",[188,1912,1913],{"class":227},"(group, path, sheet_name",[188,1915,452],{"class":223},[188,1917,1918],{"class":198},"\"Detail\"",[188,1920,570],{"class":227},[188,1922,1923],{"class":190,"line":250},[188,1924,1925],{"class":198},"    \"\"\"Write one group as a formatted, self-contained report.\"\"\"\n",[188,1927,1928,1931,1934,1937,1939,1942],{"class":190,"line":290},[188,1929,1930],{"class":223},"    with",[188,1932,1933],{"class":227}," pd.ExcelWriter(path, ",[188,1935,1936],{"class":448},"engine",[188,1938,452],{"class":223},[188,1940,1941],{"class":198},"\"xlsxwriter\"",[188,1943,409],{"class":227},[188,1945,1946,1949,1951,1954,1956,1958],{"class":190,"line":343},[188,1947,1948],{"class":448},"                        datetime_format",[188,1950,452],{"class":223},[188,1952,1953],{"class":198},"\"yyyy-mm-dd\"",[188,1955,958],{"class":227},[188,1957,231],{"class":223},[188,1959,1960],{"class":227}," writer:\n",[188,1962,1963,1966,1969,1971,1974,1976,1978,1980],{"class":190,"line":381},[188,1964,1965],{"class":227},"        group.to_excel(writer, ",[188,1967,1968],{"class":448},"sheet_name",[188,1970,452],{"class":223},[188,1972,1973],{"class":227},"sheet_name, ",[188,1975,449],{"class":448},[188,1977,452],{"class":223},[188,1979,455],{"class":283},[188,1981,458],{"class":227},[188,1983,1984],{"class":190,"line":390},[188,1985,241],{"emptyLinePlaceholder":240},[188,1987,1988,1991,1993],{"class":190,"line":412},[188,1989,1990],{"class":227},"        book, sheet ",[188,1992,452],{"class":223},[188,1994,1995],{"class":227}," writer.book, writer.sheets[sheet_name]\n",[188,1997,1998,2001,2003,2006,2009,2012,2014,2016,2019,2021,2024],{"class":190,"line":431},[188,1999,2000],{"class":227},"        header ",[188,2002,452],{"class":223},[188,2004,2005],{"class":227}," book.add_format({",[188,2007,2008],{"class":198},"\"bold\"",[188,2010,2011],{"class":227},": ",[188,2013,545],{"class":283},[188,2015,262],{"class":227},[188,2017,2018],{"class":198},"\"bg_color\"",[188,2020,2011],{"class":227},[188,2022,2023],{"class":198},"\"#EEF2FF\"",[188,2025,409],{"class":227},[188,2027,2028,2031,2033,2035,2037,2040,2042,2045],{"class":190,"line":437},[188,2029,2030],{"class":198},"                                  \"border\"",[188,2032,2011],{"class":227},[188,2034,332],{"class":283},[188,2036,262],{"class":227},[188,2038,2039],{"class":198},"\"align\"",[188,2041,2011],{"class":227},[188,2043,2044],{"class":198},"\"center\"",[188,2046,2047],{"class":227},"})\n",[188,2049,2050,2053,2055,2057,2060,2062,2065],{"class":190,"line":920},[188,2051,2052],{"class":227},"        money ",[188,2054,452],{"class":223},[188,2056,2005],{"class":227},[188,2058,2059],{"class":198},"\"num_format\"",[188,2061,2011],{"class":227},[188,2063,2064],{"class":198},"\"#,##0.00\"",[188,2066,2047],{"class":227},[188,2068,2069],{"class":190,"line":972},[188,2070,241],{"emptyLinePlaceholder":240},[188,2072,2073,2076,2079,2081,2084],{"class":190,"line":984},[188,2074,2075],{"class":223},"        for",[188,2077,2078],{"class":227}," position, name ",[188,2080,323],{"class":223},[188,2082,2083],{"class":283}," enumerate",[188,2085,2086],{"class":227},"(group.columns):\n",[188,2088,2089,2092,2094,2097,2099],{"class":190,"line":989},[188,2090,2091],{"class":227},"            sheet.write(",[188,2093,54],{"class":283},[188,2095,2096],{"class":227},", position, ",[188,2098,1726],{"class":283},[188,2100,2101],{"class":227},"(name), header)\n",[188,2103,2104],{"class":190,"line":1008},[188,2105,241],{"emptyLinePlaceholder":240},[188,2107,2108,2111,2114,2116,2118],{"class":190,"line":1048},[188,2109,2110],{"class":227},"        sheet.set_column(",[188,2112,2113],{"class":198},"\"A:A\"",[188,2115,262],{"class":227},[188,2117,1792],{"class":283},[188,2119,458],{"class":227},[188,2121,2122,2124,2127,2129,2132],{"class":190,"line":1064},[188,2123,2110],{"class":227},[188,2125,2126],{"class":198},"\"B:B\"",[188,2128,262],{"class":227},[188,2130,2131],{"class":283},"18",[188,2133,458],{"class":227},[188,2135,2136,2138,2141,2143,2145],{"class":190,"line":1069},[188,2137,2110],{"class":227},[188,2139,2140],{"class":198},"\"C:C\"",[188,2142,262],{"class":227},[188,2144,60],{"class":283},[188,2146,2147],{"class":227},", money)\n",[188,2149,2150,2152,2155,2157,2159],{"class":190,"line":1086},[188,2151,2110],{"class":227},[188,2153,2154],{"class":198},"\"D:D\"",[188,2156,262],{"class":227},[188,2158,60],{"class":283},[188,2160,458],{"class":227},[188,2162,2163,2166,2168,2170,2172],{"class":190,"line":1107},[188,2164,2165],{"class":227},"        sheet.freeze_panes(",[188,2167,332],{"class":283},[188,2169,262],{"class":227},[188,2171,54],{"class":283},[188,2173,458],{"class":227},[188,2175,2176,2179,2181,2183,2185,2187,2189,2192,2194,2197,2199,2202],{"class":190,"line":1116},[188,2177,2178],{"class":227},"        sheet.autofilter(",[188,2180,54],{"class":283},[188,2182,262],{"class":227},[188,2184,54],{"class":283},[188,2186,262],{"class":227},[188,2188,1481],{"class":283},[188,2190,2191],{"class":227},"(group), ",[188,2193,1481],{"class":283},[188,2195,2196],{"class":227},"(group.columns) ",[188,2198,758],{"class":223},[188,2200,2201],{"class":283}," 1",[188,2203,458],{"class":227},[188,2205,2206],{"class":190,"line":1121},[188,2207,241],{"emptyLinePlaceholder":240},[188,2209,2210,2213,2215,2217,2219],{"class":190,"line":1138},[188,2211,2212],{"class":227},"split_to_files(pd.read_excel(",[188,2214,443],{"class":198},[188,2216,1705],{"class":227},[188,2218,567],{"class":198},[188,2220,409],{"class":227},[188,2222,2223,2226,2228],{"class":190,"line":1153},[188,2224,2225],{"class":448},"               formatter",[188,2227,452],{"class":223},[188,2229,2230],{"class":227},"write_report)\n",[10,2232,2233,2234,30],{},"Keeping the formatting in one function is what stops the outputs drifting apart. The wider vocabulary is in ",[26,2235,2237],{"href":2236},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-formatted-excel-report-with-xlsxwriter\u002F","writing a formatted Excel report with xlsxwriter",[174,2239,2241],{"id":2240},"step-5-split-into-sheets-instead","Step 5 — Split into sheets instead",[10,2243,2244],{},"When one person wants everything, organised, a single workbook is far easier to handle than a folder:",[179,2246,2248],{"className":214,"code":2247,"language":216,"meta":184,"style":184},"import pandas as pd\n\ndef split_to_sheets(df, key, path, max_sheets=60):\n    \"\"\"One sheet per distinct value, in a single workbook.\"\"\"\n    groups = list(df.groupby(key, dropna=False))\n    if len(groups) > max_sheets:\n        raise ValueError(f\"{len(groups)} groups is too many sheets to navigate\")\n\n    names = unique_names([value for value, _ in groups], max_length=31)\n\n    with pd.ExcelWriter(path, engine=\"xlsxwriter\",\n                        datetime_format=\"yyyy-mm-dd\") as writer:\n        summary = (\n            df.groupby(key, dropna=False)\n              .agg(rows=(df.columns[0], \"size\"), revenue=(\"revenue\", \"sum\"))\n              .reset_index()\n        )\n        summary.to_excel(writer, sheet_name=\"Summary\", index=False)\n\n        for value, group in groups:\n            group.to_excel(writer, sheet_name=names[value], index=False)\n\n    return path\n\nsplit_to_sheets(pd.read_excel(\"all_regions.xlsx\"), \"region\", \"by_region.xlsx\")\n",[14,2249,2250,2260,2264,2281,2286,2304,2317,2342,2346,2373,2377,2391,2405,2415,2428,2467,2472,2476,2498,2502,2512,2532,2536,2543,2547],{"__ignoreMap":184},[188,2251,2252,2254,2256,2258],{"class":190,"line":191},[188,2253,224],{"class":223},[188,2255,228],{"class":227},[188,2257,231],{"class":223},[188,2259,234],{"class":227},[188,2261,2262],{"class":190,"line":237},[188,2263,241],{"emptyLinePlaceholder":240},[188,2265,2266,2268,2271,2274,2276,2279],{"class":190,"line":244},[188,2267,891],{"class":223},[188,2269,2270],{"class":894}," split_to_sheets",[188,2272,2273],{"class":227},"(df, key, path, max_sheets",[188,2275,452],{"class":223},[188,2277,2278],{"class":283},"60",[188,2280,570],{"class":227},[188,2282,2283],{"class":190,"line":250},[188,2284,2285],{"class":198},"    \"\"\"One sheet per distinct value, in a single workbook.\"\"\"\n",[188,2287,2288,2290,2292,2294,2296,2298,2300,2302],{"class":190,"line":290},[188,2289,1420],{"class":227},[188,2291,452],{"class":223},[188,2293,1425],{"class":283},[188,2295,1428],{"class":227},[188,2297,1431],{"class":448},[188,2299,452],{"class":223},[188,2301,455],{"class":283},[188,2303,1438],{"class":227},[188,2305,2306,2308,2310,2312,2314],{"class":190,"line":343},[188,2307,1072],{"class":223},[188,2309,1449],{"class":283},[188,2311,1452],{"class":227},[188,2313,1455],{"class":223},[188,2315,2316],{"class":227}," max_sheets:\n",[188,2318,2319,2321,2323,2325,2327,2329,2331,2333,2335,2337,2340],{"class":190,"line":381},[188,2320,1463],{"class":223},[188,2322,1466],{"class":283},[188,2324,329],{"class":227},[188,2326,298],{"class":223},[188,2328,314],{"class":198},[188,2330,305],{"class":304},[188,2332,1481],{"class":283},[188,2334,1484],{"class":227},[188,2336,311],{"class":304},[188,2338,2339],{"class":198}," groups is too many sheets to navigate\"",[188,2341,458],{"class":227},[188,2343,2344],{"class":190,"line":390},[188,2345,241],{"emptyLinePlaceholder":240},[188,2347,2348,2350,2352,2354,2356,2358,2360,2363,2366,2368,2371],{"class":190,"line":412},[188,2349,1559],{"class":227},[188,2351,452],{"class":223},[188,2353,1564],{"class":227},[188,2355,556],{"class":223},[188,2357,1569],{"class":227},[188,2359,323],{"class":223},[188,2361,2362],{"class":227}," groups], ",[188,2364,2365],{"class":448},"max_length",[188,2367,452],{"class":223},[188,2369,2370],{"class":283},"31",[188,2372,458],{"class":227},[188,2374,2375],{"class":190,"line":431},[188,2376,241],{"emptyLinePlaceholder":240},[188,2378,2379,2381,2383,2385,2387,2389],{"class":190,"line":437},[188,2380,1930],{"class":223},[188,2382,1933],{"class":227},[188,2384,1936],{"class":448},[188,2386,452],{"class":223},[188,2388,1941],{"class":198},[188,2390,409],{"class":227},[188,2392,2393,2395,2397,2399,2401,2403],{"class":190,"line":920},[188,2394,1948],{"class":448},[188,2396,452],{"class":223},[188,2398,1953],{"class":198},[188,2400,958],{"class":227},[188,2402,231],{"class":223},[188,2404,1960],{"class":227},[188,2406,2407,2410,2412],{"class":190,"line":972},[188,2408,2409],{"class":227},"        summary ",[188,2411,452],{"class":223},[188,2413,2414],{"class":227}," (\n",[188,2416,2417,2420,2422,2424,2426],{"class":190,"line":984},[188,2418,2419],{"class":227},"            df.groupby(key, ",[188,2421,1431],{"class":448},[188,2423,452],{"class":223},[188,2425,455],{"class":283},[188,2427,458],{"class":227},[188,2429,2430,2433,2435,2437,2440,2442,2445,2448,2450,2453,2455,2457,2460,2462,2465],{"class":190,"line":989},[188,2431,2432],{"class":227},"              .agg(",[188,2434,1740],{"class":448},[188,2436,452],{"class":223},[188,2438,2439],{"class":227},"(df.columns[",[188,2441,54],{"class":283},[188,2443,2444],{"class":227},"], ",[188,2446,2447],{"class":198},"\"size\"",[188,2449,1705],{"class":227},[188,2451,2452],{"class":448},"revenue",[188,2454,452],{"class":223},[188,2456,329],{"class":227},[188,2458,2459],{"class":198},"\"revenue\"",[188,2461,262],{"class":227},[188,2463,2464],{"class":198},"\"sum\"",[188,2466,1438],{"class":227},[188,2468,2469],{"class":190,"line":1008},[188,2470,2471],{"class":227},"              .reset_index()\n",[188,2473,2474],{"class":190,"line":1048},[188,2475,1514],{"class":227},[188,2477,2478,2481,2483,2485,2488,2490,2492,2494,2496],{"class":190,"line":1064},[188,2479,2480],{"class":227},"        summary.to_excel(writer, ",[188,2482,1968],{"class":448},[188,2484,452],{"class":223},[188,2486,2487],{"class":198},"\"Summary\"",[188,2489,262],{"class":227},[188,2491,449],{"class":448},[188,2493,452],{"class":223},[188,2495,455],{"class":283},[188,2497,458],{"class":227},[188,2499,2500],{"class":190,"line":1069},[188,2501,241],{"emptyLinePlaceholder":240},[188,2503,2504,2506,2508,2510],{"class":190,"line":1086},[188,2505,2075],{"class":223},[188,2507,1595],{"class":227},[188,2509,323],{"class":223},[188,2511,1600],{"class":227},[188,2513,2514,2517,2519,2521,2524,2526,2528,2530],{"class":190,"line":1107},[188,2515,2516],{"class":227},"            group.to_excel(writer, ",[188,2518,1968],{"class":448},[188,2520,452],{"class":223},[188,2522,2523],{"class":227},"names[value], ",[188,2525,449],{"class":448},[188,2527,452],{"class":223},[188,2529,455],{"class":283},[188,2531,458],{"class":227},[188,2533,2534],{"class":190,"line":1116},[188,2535,241],{"emptyLinePlaceholder":240},[188,2537,2538,2540],{"class":190,"line":1121},[188,2539,1110],{"class":223},[188,2541,2542],{"class":227}," path\n",[188,2544,2545],{"class":190,"line":1138},[188,2546,241],{"emptyLinePlaceholder":240},[188,2548,2549,2552,2554,2556,2558,2560,2563],{"class":190,"line":1153},[188,2550,2551],{"class":227},"split_to_sheets(pd.read_excel(",[188,2553,443],{"class":198},[188,2555,1705],{"class":227},[188,2557,567],{"class":198},[188,2559,262],{"class":227},[188,2561,2562],{"class":198},"\"by_region.xlsx\"",[188,2564,458],{"class":227},[10,2566,2567,2568,2570,2571,30],{},"Sheet names cap at 31 characters, hence the shorter ",[14,2569,2365],{},". Adding a summary sheet first gives the reader somewhere to land — the pattern described in ",[26,2572,2574],{"href":2573},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python\u002F","adding a summary sheet to an Excel report",[174,2576,2578],{"id":2577},"common-pitfalls-and-fixes","Common pitfalls and fixes",[2580,2581,2582,2598],"table",{},[2583,2584,2585],"thead",{},[2586,2587,2588,2592,2595],"tr",{},[2589,2590,2591],"th",{},"Symptom",[2589,2593,2594],{},"Cause",[2589,2596,2597],{},"Fix",[2599,2600,2601,2618,2632,2643,2657,2670,2681],"tbody",{},[2586,2602,2603,2607,2612],{},[2604,2605,2606],"td",{},"Rows silently missing from every output",[2604,2608,2609,2611],{},[14,2610,16],{}," drops missing keys",[2604,2613,2614,2615,30],{},"Pass ",[14,2616,2617],{},"dropna=False",[2586,2619,2620,2626,2629],{},[2604,2621,2622,2625],{},[14,2623,2624],{},"OSError: Invalid argument"," writing a file",[2604,2627,2628],{},"Illegal character in the group value",[2604,2630,2631],{},"Sanitise the filename.",[2586,2633,2634,2637,2640],{},[2604,2635,2636],{},"Two groups overwrote each other",[2604,2638,2639],{},"Sanitising produced the same name",[2604,2641,2642],{},"Resolve collisions with a counter.",[2586,2644,2645,2648,2651],{},[2604,2646,2647],{},"Thousands of files created",[2604,2649,2650],{},"Split column had far more values than expected",[2604,2652,2653,2654,2656],{},"Guard with a ",[14,2655,1764],{}," limit.",[2586,2658,2659,2664,2667],{},[2604,2660,2661],{},[14,2662,2663],{},"InvalidWorksheetName",[2604,2665,2666],{},"Sheet name over 31 characters",[2604,2668,2669],{},"Cap the length when splitting to sheets.",[2586,2671,2672,2675,2678],{},[2604,2673,2674],{},"Outputs look inconsistent",[2604,2676,2677],{},"Formatting inline in the loop, drifted over time",[2604,2679,2680],{},"One shared formatter function.",[2586,2682,2683,2686,2689],{},[2604,2684,2685],{},"Totals do not match the source",[2604,2687,2688],{},"Rows dropped or duplicated",[2604,2690,2691],{},"Assert the row counts sum back.",[174,2693,2695],{"id":2694},"performance-and-scale-notes","Performance and scale notes",[10,2697,2698,2700],{},[14,2699,16],{}," is a single pass and cheap. The cost is one workbook write per group, and each write has a fixed overhead independent of size — so a split into three hundred small files costs far more than one file three hundred times larger.",[10,2702,2703,2704,2708,2709,2712],{},"Two things follow. ",[2705,2706,2707],"strong",{},"Guard the group count",", as above, because the cost is linear in files and people rarely intend four thousand of them. And ",[2705,2710,2711],{},"parallelise across groups"," when the count is genuinely high, since each write is independent:",[179,2714,2716],{"className":214,"code":2715,"language":216,"meta":184,"style":184},"from concurrent.futures import ProcessPoolExecutor\nfrom pathlib import Path\nimport pandas as pd\n\ndef _write_one(args):\n    stem, records, columns = args\n    frame = pd.DataFrame(records, columns=columns)\n    path = Path(\"split\") \u002F f\"{stem}.xlsx\"\n    write_report(frame, path)\n    return stem, len(frame)\n\nif __name__ == \"__main__\":\n    df = pd.read_excel(\"all_regions.xlsx\")\n    names = unique_names(df[\"region\"].unique())\n    payload = [\n        (names[value], group.to_records(index=False).tolist(), list(df.columns))\n        for value, group in df.groupby(\"region\", dropna=False)\n    ]\n    with ProcessPoolExecutor(max_workers=4) as pool:\n        for stem, rows in pool.map(_write_one, payload):\n            print(f\"{stem:\u003C20} {rows} rows\")\n",[14,2717,2718,2730,2740,2750,2754,2764,2774,2792,2819,2824,2836,2840,2855,2868,2882,2892,2912,2934,2939,2961,2973],{"__ignoreMap":184},[188,2719,2720,2722,2725,2727],{"class":190,"line":191},[188,2721,477],{"class":223},[188,2723,2724],{"class":227}," concurrent.futures ",[188,2726,224],{"class":223},[188,2728,2729],{"class":227}," ProcessPoolExecutor\n",[188,2731,2732,2734,2736,2738],{"class":190,"line":237},[188,2733,477],{"class":223},[188,2735,480],{"class":227},[188,2737,224],{"class":223},[188,2739,485],{"class":227},[188,2741,2742,2744,2746,2748],{"class":190,"line":244},[188,2743,224],{"class":223},[188,2745,228],{"class":227},[188,2747,231],{"class":223},[188,2749,234],{"class":227},[188,2751,2752],{"class":190,"line":250},[188,2753,241],{"emptyLinePlaceholder":240},[188,2755,2756,2758,2761],{"class":190,"line":290},[188,2757,891],{"class":223},[188,2759,2760],{"class":894}," _write_one",[188,2762,2763],{"class":227},"(args):\n",[188,2765,2766,2769,2771],{"class":190,"line":343},[188,2767,2768],{"class":227},"    stem, records, columns ",[188,2770,452],{"class":223},[188,2772,2773],{"class":227}," args\n",[188,2775,2776,2779,2781,2784,2787,2789],{"class":190,"line":381},[188,2777,2778],{"class":227},"    frame ",[188,2780,452],{"class":223},[188,2782,2783],{"class":227}," pd.DataFrame(records, ",[188,2785,2786],{"class":448},"columns",[188,2788,452],{"class":223},[188,2790,2791],{"class":227},"columns)\n",[188,2793,2794,2797,2799,2801,2803,2805,2807,2809,2811,2813,2815,2817],{"class":190,"line":390},[188,2795,2796],{"class":227},"    path ",[188,2798,452],{"class":223},[188,2800,527],{"class":227},[188,2802,530],{"class":198},[188,2804,958],{"class":227},[188,2806,578],{"class":223},[188,2808,581],{"class":223},[188,2810,314],{"class":198},[188,2812,305],{"class":304},[188,2814,1292],{"class":227},[188,2816,311],{"class":304},[188,2818,1626],{"class":198},[188,2820,2821],{"class":190,"line":412},[188,2822,2823],{"class":227},"    write_report(frame, path)\n",[188,2825,2826,2828,2831,2833],{"class":190,"line":431},[188,2827,1110],{"class":223},[188,2829,2830],{"class":227}," stem, ",[188,2832,1481],{"class":283},[188,2834,2835],{"class":227},"(frame)\n",[188,2837,2838],{"class":190,"line":437},[188,2839,241],{"emptyLinePlaceholder":240},[188,2841,2842,2844,2847,2850,2853],{"class":190,"line":920},[188,2843,931],{"class":223},[188,2845,2846],{"class":283}," __name__",[188,2848,2849],{"class":223}," ==",[188,2851,2852],{"class":198}," \"__main__\"",[188,2854,1083],{"class":227},[188,2856,2857,2860,2862,2864,2866],{"class":190,"line":972},[188,2858,2859],{"class":227},"    df ",[188,2861,452],{"class":223},[188,2863,509],{"class":227},[188,2865,443],{"class":198},[188,2867,458],{"class":227},[188,2869,2870,2872,2874,2877,2879],{"class":190,"line":984},[188,2871,1559],{"class":227},[188,2873,452],{"class":223},[188,2875,2876],{"class":227}," unique_names(df[",[188,2878,567],{"class":198},[188,2880,2881],{"class":227},"].unique())\n",[188,2883,2884,2887,2889],{"class":190,"line":989},[188,2885,2886],{"class":227},"    payload ",[188,2888,452],{"class":223},[188,2890,2891],{"class":227}," [\n",[188,2893,2894,2897,2899,2901,2903,2906,2909],{"class":190,"line":1008},[188,2895,2896],{"class":227},"        (names[value], group.to_records(",[188,2898,449],{"class":448},[188,2900,452],{"class":223},[188,2902,455],{"class":283},[188,2904,2905],{"class":227},").tolist(), ",[188,2907,2908],{"class":283},"list",[188,2910,2911],{"class":227},"(df.columns))\n",[188,2913,2914,2916,2918,2920,2922,2924,2926,2928,2930,2932],{"class":190,"line":1048},[188,2915,2075],{"class":223},[188,2917,1595],{"class":227},[188,2919,323],{"class":223},[188,2921,564],{"class":227},[188,2923,567],{"class":198},[188,2925,262],{"class":227},[188,2927,1431],{"class":448},[188,2929,452],{"class":223},[188,2931,455],{"class":283},[188,2933,458],{"class":227},[188,2935,2936],{"class":190,"line":1064},[188,2937,2938],{"class":227},"    ]\n",[188,2940,2941,2943,2946,2949,2951,2954,2956,2958],{"class":190,"line":1069},[188,2942,1930],{"class":223},[188,2944,2945],{"class":227}," ProcessPoolExecutor(",[188,2947,2948],{"class":448},"max_workers",[188,2950,452],{"class":223},[188,2952,2953],{"class":283},"4",[188,2955,958],{"class":227},[188,2957,231],{"class":223},[188,2959,2960],{"class":227}," pool:\n",[188,2962,2963,2965,2968,2970],{"class":190,"line":1086},[188,2964,2075],{"class":223},[188,2966,2967],{"class":227}," stem, rows ",[188,2969,323],{"class":223},[188,2971,2972],{"class":227}," pool.map(_write_one, payload):\n",[188,2974,2975,2978,2980,2982,2984,2986,2988,2991,2993,2995,2997,2999,3002],{"class":190,"line":1107},[188,2976,2977],{"class":283},"            print",[188,2979,329],{"class":227},[188,2981,298],{"class":223},[188,2983,314],{"class":198},[188,2985,305],{"class":304},[188,2987,1292],{"class":227},[188,2989,2990],{"class":223},":\u003C20",[188,2992,311],{"class":304},[188,2994,1737],{"class":304},[188,2996,1740],{"class":227},[188,2998,311],{"class":304},[188,3000,3001],{"class":198}," rows\"",[188,3003,458],{"class":227},[10,3005,3006],{},"Passing records rather than DataFrames keeps the pickling cost down, since each worker rebuilds its own frame from plain tuples.",[10,3008,3009],{},"Finally, verify. A split should conserve rows exactly, and asserting it catches both a dropped group and a duplicated one:",[179,3011,3013],{"className":214,"code":3012,"language":216,"meta":184,"style":184},"import pandas as pd\n\nsource = pd.read_excel(\"all_regions.xlsx\")\nwritten = split_to_files(source, \"region\")\n\ntotal = sum(rows for _, _, rows in written)\nassert total == len(source), f\"split produced {total} rows from {len(source)}\"\nprint(\"row counts reconcile\")\n",[14,3014,3015,3025,3029,3042,3056,3060,3083,3125],{"__ignoreMap":184},[188,3016,3017,3019,3021,3023],{"class":190,"line":191},[188,3018,224],{"class":223},[188,3020,228],{"class":227},[188,3022,231],{"class":223},[188,3024,234],{"class":227},[188,3026,3027],{"class":190,"line":237},[188,3028,241],{"emptyLinePlaceholder":240},[188,3030,3031,3034,3036,3038,3040],{"class":190,"line":244},[188,3032,3033],{"class":227},"source ",[188,3035,452],{"class":223},[188,3037,509],{"class":227},[188,3039,443],{"class":198},[188,3041,458],{"class":227},[188,3043,3044,3047,3049,3052,3054],{"class":190,"line":250},[188,3045,3046],{"class":227},"written ",[188,3048,452],{"class":223},[188,3050,3051],{"class":227}," split_to_files(source, ",[188,3053,567],{"class":198},[188,3055,458],{"class":227},[188,3057,3058],{"class":190,"line":290},[188,3059,241],{"emptyLinePlaceholder":240},[188,3061,3062,3065,3067,3070,3073,3075,3078,3080],{"class":190,"line":343},[188,3063,3064],{"class":227},"total ",[188,3066,452],{"class":223},[188,3068,3069],{"class":283}," sum",[188,3071,3072],{"class":227},"(rows ",[188,3074,556],{"class":223},[188,3076,3077],{"class":227}," _, _, rows ",[188,3079,323],{"class":223},[188,3081,3082],{"class":227}," written)\n",[188,3084,3085,3088,3091,3094,3096,3099,3101,3104,3106,3109,3111,3114,3116,3118,3121,3123],{"class":190,"line":381},[188,3086,3087],{"class":223},"assert",[188,3089,3090],{"class":227}," total ",[188,3092,3093],{"class":223},"==",[188,3095,1449],{"class":283},[188,3097,3098],{"class":227},"(source), ",[188,3100,298],{"class":223},[188,3102,3103],{"class":198},"\"split produced ",[188,3105,305],{"class":304},[188,3107,3108],{"class":227},"total",[188,3110,311],{"class":304},[188,3112,3113],{"class":198}," rows from ",[188,3115,305],{"class":304},[188,3117,1481],{"class":283},[188,3119,3120],{"class":227},"(source)",[188,3122,311],{"class":304},[188,3124,1307],{"class":198},[188,3126,3127,3129,3131,3134],{"class":190,"line":390},[188,3128,1124],{"class":283},[188,3130,329],{"class":227},[188,3132,3133],{"class":198},"\"row counts reconcile\"",[188,3135,458],{"class":227},[10,3137,3138,3139,30],{},"That check belongs in the same suite as the other output assertions described in ",[26,3140,3142],{"href":3141},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Ftest-excel-output-with-pytest\u002F","testing Excel output with pytest",[174,3144,3146],{"id":3145},"conclusion","Conclusion",[10,3148,3149,3150,3152,3153,3155],{},"Splitting a sheet is ",[14,3151,16],{}," plus a write, wrapped in the guards that make it safe on real data. Pass ",[14,3154,2617],{}," so rows with a missing key are not silently lost, sanitise the group values into filenames that are legal everywhere and resolve the collisions that sanitising creates, and refuse to run when the group count is far higher than you expected. Route every output through one formatter so all the files look alike, choose sheets over files when a single person wants the whole picture, and assert that the row counts reconcile before anything is sent.",[174,3157,3159],{"id":3158},"frequently-asked-questions","Frequently asked questions",[10,3161,3162,3165],{},[2705,3163,3164],{},"Should I split into separate files or separate sheets?","\nSeparate files when each group goes to a different person, because you can send exactly one workbook to each. Separate sheets when one person wants the whole picture but organised by group — a single file is far easier to open and navigate.",[10,3167,3168,3171,3172,3174,3175,3178],{},[2705,3169,3170],{},"How do I make safe filenames from the group values?","\nStrip characters that are illegal on Windows, collapse whitespace, cap the length, and handle collisions after sanitising. A region called ",[14,3173,615],{}," and one called ",[14,3176,3177],{},"North-South"," both become the same name otherwise.",[10,3180,3181,3184,3185,3187,3188,3190,3191,3193,3194,3197],{},[2705,3182,3183],{},"What if a value is blank or missing?","\nDecide explicitly rather than letting ",[14,3186,16],{}," drop it. Pass ",[14,3189,2617],{}," to ",[14,3192,16],{}," and map the missing key to a name like ",[14,3195,3196],{},"unspecified",", or route those rows to a separate exceptions file.",[10,3199,3200,3203],{},[2705,3201,3202],{},"Can I keep the formatting on every split file?","\nYes, but not by copying — write each output through the same formatting function so all of them are produced identically. Copying a styled template and filling it works too when the layout is fixed.",[10,3205,3206,3209],{},[2705,3207,3208],{},"What happens with hundreds of groups?","\nGuard against it. A split on an unexpectedly high-cardinality column can produce thousands of files, so check the group count first and refuse if it exceeds a sensible limit.",[174,3211,3213],{"id":3212},"related","Related",[3215,3216,3217,3224,3231,3238,3245],"ul",{},[3218,3219,3220,3221,3223],"li",{},"Up to the parent: ",[26,3222,29],{"href":28}," — the sheet-level operations behind this.",[3218,3225,3226,3230],{},[26,3227,3229],{"href":3228},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fcombine-multiple-excel-files-into-one-python\u002F","Combine Multiple Excel Files into One with Python"," — the inverse operation.",[3218,3232,3233,3237],{},[26,3234,3236],{"href":3235},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-excel-report-per-region-in-a-loop\u002F","Generate One Excel Report per Region in a Loop"," — the same fan-out driven from a template.",[3218,3239,3240,3244],{},[26,3241,3243],{"href":3242},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fwrite-multiple-dataframes-to-one-excel-file\u002F","Write Multiple DataFrames to One Excel File"," — the split-to-sheets mechanics in more depth.",[3218,3246,3247,3250],{},[26,3248,3249],{"href":2236},"Write a Formatted Excel Report with xlsxwriter"," — the formatter each output shares.",[3252,3253,3254],"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 .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}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}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}",{"title":184,"searchDepth":237,"depth":237,"links":3256},[3257,3258,3259,3260,3261,3262,3263,3264,3265,3266,3267],{"id":176,"depth":237,"text":177},{"id":461,"depth":237,"text":462},{"id":619,"depth":237,"text":620},{"id":1341,"depth":237,"text":1342},{"id":1768,"depth":237,"text":1769},{"id":2240,"depth":237,"text":2241},{"id":2577,"depth":237,"text":2578},{"id":2694,"depth":237,"text":2695},{"id":3145,"depth":237,"text":3146},{"id":3158,"depth":237,"text":3159},{"id":3212,"depth":237,"text":3213},"2026-08-15","Break a combined export into one workbook per region, customer or month — groupby, safe filenames, sheets vs files, and keeping formatting on every split output.","md",[3272,3274,3276,3278,3280],{"q":3164,"a":3273},"Separate files when each group goes to a different person, because you can send exactly one workbook to each. Separate sheets when one person wants the whole picture but organised by group — a single file is far easier to open and navigate.",{"q":3170,"a":3275},"Strip characters that are illegal on Windows, collapse whitespace, cap the length, and handle collisions after sanitising. A region called North\u002FSouth and one called North-South both become the same name otherwise.",{"q":3183,"a":3277},"Decide explicitly rather than letting groupby drop it. Pass dropna=False to groupby and map the missing key to a name like \"unspecified\", or route those rows to a separate exceptions file.",{"q":3202,"a":3279},"Yes, but not by copying — write each output through the same formatting function so all of them are produced identically. Copying a styled template and filling it works too when the layout is fixed.",{"q":3208,"a":3281},"Guard against it. A split on an unexpectedly high-cardinality column can produce thousands of files, so check the group count first and refuse if it exceeds a sensible limit.",{},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value",{"title":3285,"description":3286},"Split an Excel File by Column Value with Python","Use pandas groupby to split one sheet into many workbooks or many sheets — filename sanitising, formatting each output, a manifest, and handling high-cardinality splits.","split-one-excel-sheet-into-multiple-files-by-value","getting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value\u002Findex","how-to","-Rh49RQ_DqGLz6hk3z2zM8_ErTj14ge31ixf-VncVB8",[3292,3296],{"title":3293,"path":3294,"stem":3295,"children":-1},"Rename, Reorder and Delete Excel Sheets with openpyxl","\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Frename-reorder-and-delete-excel-sheets-with-openpyxl","getting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Frename-reorder-and-delete-excel-sheets-with-openpyxl\u002Findex",{"title":3297,"path":3298,"stem":3299,"children":-1},"Writing DataFrames to Excel with Pandas","\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Findex",1786800028708]