[{"data":1,"prerenderedAt":2078},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows":2044,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows":2053,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows":2058,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows":2059},{"id":4,"title":5,"body":6,"dateModified":2011,"datePublished":2011,"description":2012,"extension":2013,"faq":2014,"keyword":2023,"kind":2024,"meta":2025,"navigation":260,"path":2033,"seo":2034,"slug":2037,"stem":2038,"topics":2039,"type":2042,"__hash__":2043},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows\u002Findex.md","Split a Large Excel File into Files of N Rows",{"type":7,"value":8,"toc":1998},"minimark",[9,19,24,53,59,199,203,210,549,585,588,592,603,1104,1117,1234,1238,1248,1576,1583,1587,1590,1744,1754,1758,1765,1841,1845,1853,1856,1860,1916,1920,1929,1933,1943,1949,1955,1961,1965,1994],[10,11,12,13,18],"p",{},"An import tool that accepts at most 10,000 rows per file. A web form with a 5 MB upload limit. A colleague whose laptop freezes opening a 300,000-row workbook. The fix is the same each time: cut the sheet into parts of a fixed number of rows, each with the header row, named so they sort in order. With pandas this is a few lines if the file fits in memory; with openpyxl's streaming modes it works on files of any size with flat memory use. Either way, the job ends with a check that the parts add up to the original. This page is part of ",[14,15,17],"a",{"href":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002F","Working with Large Excel Files in Python",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," pandas",[35,51,52],{"class":45}," openpyxl\n",[10,54,55,58],{},[32,56,57],{},"transactions.xlsx"," has one sheet with 120,000 transactions: an ID, a date, an account code, an amount and a currency. The target is parts of at most 50,000 rows each — two full parts and one of 20,000.",[60,61,69,70,69,74,69,78,69,86,69,96,69,107,69,113,69,118,69,126,69,133,69,139,69,144,69,147,69,152,69,155,69,160,69,163,69,167,69,170,69,173,69,178,69,184,69,189,69,193,69,196],"svg",{"viewBox":62,"role":63,"ariaLabelledBy":64,"xmlns":67,"style":68},"4 0 752 332","img",[65,66],"sp-fan-t","sp-fan-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[71,72,73],"title",{"id":65},"One sheet, three parts, one header each",[75,76,77],"desc",{"id":66},"A 120,000-row sheet fans out into three files. The first two have 50,000 data rows each and the third has 20,000; each starts with the same header row.",[79,80],"rect",{"x":81,"y":82,"width":83,"height":84,"fill":85},"4","0","752","332","#ffffff",[79,87],{"x":88,"y":89,"width":90,"height":91,"rx":92,"fill":93,"stroke":94,"style":95},"20.0","119.5","220.0","93.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[97,98,100],"g",{"transform":99},"translate(118.0,132.5) scale(1.000)",[101,102],"path",{"d":103,"fill":104,"stroke":105,"style":106},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[108,109,57],"text",{"x":110,"y":111,"style":112},"130.0","179.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle",[108,114,117],{"x":110,"y":115,"style":116},"198.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","120,000 rows + header",[79,119],{"x":120,"y":121,"width":122,"height":123,"rx":92,"fill":124,"stroke":125,"style":95},"490.0","16.0","250.0","92.0","#f0f4ff","var(--brand,#5b5cf0)",[97,127,129],{"transform":128},"translate(603.0,29.0) scale(1.000)",[101,130],{"d":131,"fill":104,"stroke":132,"style":106},"M6 3h9l4 4v14H6z M15 3v4h4","var(--brand-strong,#4338ca)",[108,134,138],{"x":135,"y":136,"style":137},"615.0","75.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","transactions_part_01.xlsx",[108,140,143],{"x":135,"y":141,"style":142},"94.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","rows 1–50,000",[101,145],{"d":146,"fill":104,"stroke":125,"style":95},"M244.0,166.0 C364.0,166.0 364.0,62.0 480.0,62.0",[148,149],"polygon",{"points":150,"fill":151},"484.0,62.0 475.0,67.0 475.0,57.0","#5b5cf0",[79,153],{"x":120,"y":154,"width":122,"height":123,"rx":92,"fill":124,"stroke":125,"style":95},"120.0",[97,156,158],{"transform":157},"translate(603.0,133.0) scale(1.000)",[101,159],{"d":131,"fill":104,"stroke":132,"style":106},[108,161,162],{"x":135,"y":111,"style":137},"transactions_part_02.xlsx",[108,164,166],{"x":135,"y":165,"style":142},"198.0","rows 50,001–100,000",[101,168],{"d":169,"fill":104,"stroke":125,"style":95},"M244.0,166.0 C364.0,166.0 364.0,166.0 480.0,166.0",[148,171],{"points":172,"fill":151},"484.0,166.0 475.0,171.0 475.0,161.0",[79,174],{"x":120,"y":175,"width":122,"height":123,"rx":92,"fill":176,"stroke":177,"style":95},"224.0","#d9f4f1","var(--teal,#0f9488)",[97,179,181],{"transform":180},"translate(603.0,237.0) scale(1.000)",[101,182],{"d":131,"fill":104,"stroke":183,"style":106},"var(--teal-ink,#0b6157)",[108,185,188],{"x":135,"y":186,"style":187},"283.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","transactions_part_03.xlsx",[108,190,192],{"x":135,"y":191,"style":142},"302.0","rows 100,001–120,000",[101,194],{"d":195,"fill":104,"stroke":125,"style":95},"M244.0,166.0 C364.0,166.0 364.0,270.0 480.0,270.0",[148,197],{"points":198,"fill":151},"484.0,270.0 475.0,275.0 475.0,265.0",[20,200,202],{"id":201},"step-1-the-pandas-version","Step 1 — The pandas version",[10,204,205,206,209],{},"If the file loads comfortably, read it once and write consecutive slices with ",[32,207,208],{},"iloc",":",[25,211,215],{"className":212,"code":213,"language":214,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import math\nfrom pathlib import Path\nimport pandas as pd\n\nROWS = 50_000\ndf = pd.read_excel(\"transactions.xlsx\", sheet_name=\"Transactions\", dtype={\"Account\": str})\nparts = math.ceil(len(df) \u002F ROWS)\nout = Path(\"parts_pandas\")\nout.mkdir(exist_ok=True)\nfor i in range(parts):\n    chunk = df.iloc[i * ROWS:(i + 1) * ROWS]\n    chunk.to_excel(out \u002F f\"transactions_part_{i + 1:02d}.xlsx\", index=False)\n    print(f\"part {i + 1}: rows {i * ROWS + 1}–{i * ROWS + len(chunk)}\")\n","python",[32,216,217,227,241,255,262,275,324,350,366,382,400,435,480],{"__ignoreMap":30},[35,218,219,223],{"class":37,"line":38},[35,220,222],{"class":221},"s-kum","import",[35,224,226],{"class":225},"skGVy"," math\n",[35,228,230,233,236,238],{"class":37,"line":229},2,[35,231,232],{"class":221},"from",[35,234,235],{"class":225}," pathlib ",[35,237,222],{"class":221},[35,239,240],{"class":225}," Path\n",[35,242,244,246,249,252],{"class":37,"line":243},3,[35,245,222],{"class":221},[35,247,248],{"class":225}," pandas ",[35,250,251],{"class":221},"as",[35,253,254],{"class":225}," pd\n",[35,256,258],{"class":37,"line":257},4,[35,259,261],{"emptyLinePlaceholder":260},true,"\n",[35,263,265,269,272],{"class":37,"line":264},5,[35,266,268],{"class":267},"sP0c6","ROWS",[35,270,271],{"class":221}," =",[35,273,274],{"class":267}," 50_000\n",[35,276,278,281,284,287,290,293,297,299,302,304,307,309,312,315,318,321],{"class":37,"line":277},6,[35,279,280],{"class":225},"df ",[35,282,283],{"class":221},"=",[35,285,286],{"class":225}," pd.read_excel(",[35,288,289],{"class":45},"\"transactions.xlsx\"",[35,291,292],{"class":225},", ",[35,294,296],{"class":295},"sa561","sheet_name",[35,298,283],{"class":221},[35,300,301],{"class":45},"\"Transactions\"",[35,303,292],{"class":225},[35,305,306],{"class":295},"dtype",[35,308,283],{"class":221},[35,310,311],{"class":225},"{",[35,313,314],{"class":45},"\"Account\"",[35,316,317],{"class":225},": ",[35,319,320],{"class":267},"str",[35,322,323],{"class":225},"})\n",[35,325,327,330,332,335,338,341,344,347],{"class":37,"line":326},7,[35,328,329],{"class":225},"parts ",[35,331,283],{"class":221},[35,333,334],{"class":225}," math.ceil(",[35,336,337],{"class":267},"len",[35,339,340],{"class":225},"(df) ",[35,342,343],{"class":221},"\u002F",[35,345,346],{"class":267}," ROWS",[35,348,349],{"class":225},")\n",[35,351,353,356,358,361,364],{"class":37,"line":352},8,[35,354,355],{"class":225},"out ",[35,357,283],{"class":221},[35,359,360],{"class":225}," Path(",[35,362,363],{"class":45},"\"parts_pandas\"",[35,365,349],{"class":225},[35,367,369,372,375,377,380],{"class":37,"line":368},9,[35,370,371],{"class":225},"out.mkdir(",[35,373,374],{"class":295},"exist_ok",[35,376,283],{"class":221},[35,378,379],{"class":267},"True",[35,381,349],{"class":225},[35,383,385,388,391,394,397],{"class":37,"line":384},10,[35,386,387],{"class":221},"for",[35,389,390],{"class":225}," i ",[35,392,393],{"class":221},"in",[35,395,396],{"class":267}," range",[35,398,399],{"class":225},"(parts):\n",[35,401,403,406,408,411,414,416,419,422,425,428,430,432],{"class":37,"line":402},11,[35,404,405],{"class":225},"    chunk ",[35,407,283],{"class":221},[35,409,410],{"class":225}," df.iloc[i ",[35,412,413],{"class":221},"*",[35,415,346],{"class":267},[35,417,418],{"class":225},":(i ",[35,420,421],{"class":221},"+",[35,423,424],{"class":267}," 1",[35,426,427],{"class":225},") ",[35,429,413],{"class":221},[35,431,346],{"class":267},[35,433,434],{"class":225},"]\n",[35,436,438,441,443,446,449,452,455,457,459,462,465,468,470,473,475,478],{"class":37,"line":437},12,[35,439,440],{"class":225},"    chunk.to_excel(out ",[35,442,343],{"class":221},[35,444,445],{"class":221}," f",[35,447,448],{"class":45},"\"transactions_part_",[35,450,311],{"class":451},"sSjpA",[35,453,454],{"class":225},"i ",[35,456,421],{"class":221},[35,458,424],{"class":267},[35,460,461],{"class":221},":02d",[35,463,464],{"class":451},"}",[35,466,467],{"class":45},".xlsx\"",[35,469,292],{"class":225},[35,471,472],{"class":295},"index",[35,474,283],{"class":221},[35,476,477],{"class":267},"False",[35,479,349],{"class":225},[35,481,483,486,489,492,495,497,499,501,503,505,508,510,512,514,516,519,521,523,526,528,530,532,534,536,539,542,544,547],{"class":37,"line":482},13,[35,484,485],{"class":267},"    print",[35,487,488],{"class":225},"(",[35,490,491],{"class":221},"f",[35,493,494],{"class":45},"\"part ",[35,496,311],{"class":451},[35,498,454],{"class":225},[35,500,421],{"class":221},[35,502,424],{"class":267},[35,504,464],{"class":451},[35,506,507],{"class":45},": rows ",[35,509,311],{"class":451},[35,511,454],{"class":225},[35,513,413],{"class":221},[35,515,346],{"class":267},[35,517,518],{"class":221}," +",[35,520,424],{"class":267},[35,522,464],{"class":451},[35,524,525],{"class":45},"–",[35,527,311],{"class":451},[35,529,454],{"class":225},[35,531,413],{"class":221},[35,533,346],{"class":267},[35,535,518],{"class":221},[35,537,538],{"class":267}," len",[35,540,541],{"class":225},"(chunk)",[35,543,464],{"class":451},[35,545,546],{"class":45},"\"",[35,548,349],{"class":225},[10,550,551,554,555,292,558,561,562,565,566,569,570,572,573,576,577,580,581,584],{},[32,552,553],{},"math.ceil"," rounds the number of parts up so the last, shorter part is included. The zero-padded number — ",[32,556,557],{},"01",[32,559,560],{},"02"," — keeps the files in order in a file listing even past nine parts; ",[32,563,564],{},"part_10"," sorts after ",[32,567,568],{},"part_09",", whereas ",[32,571,564],{}," sorts before ",[32,574,575],{},"part_2",". ",[32,578,579],{},"to_excel"," writes the header into every part automatically, and ",[32,582,583],{},"index=False"," stops pandas adding a column of row numbers that the importer would not expect.",[10,586,587],{},"Reading the whole sheet first is the limitation: memory holds the full DataFrame, and for a workbook of a few hundred thousand rows that is fine; for millions it is not.",[20,589,591],{"id":590},"step-2-streaming-read-only-in-write-only-out","Step 2 — Streaming: read-only in, write-only out",[10,593,594,595,598,599,602],{},"openpyxl can read a sheet as a stream of rows (",[32,596,597],{},"read_only=True",") and write new workbooks as a stream (",[32,600,601],{},"write_only=True","). Combining them moves rows from the source to the parts without ever holding more than one row plus the open output file:",[25,604,606],{"className":212,"code":605,"language":214,"meta":30,"style":30},"from openpyxl import Workbook, load_workbook\n\ndef split_stream(path, rows_per_part, out_dir, sheet=None, transform=None):\n    out_dir = Path(out_dir)\n    out_dir.mkdir(exist_ok=True)\n    src = load_workbook(path, read_only=True)\n    ws = src[sheet] if sheet else src.worksheets[0]\n    rows = ws.iter_rows(values_only=True)\n    header = next(rows)\n    written, part, count, book, sheet_out = [], 0, rows_per_part, None, None\n    for row in rows:\n        if count == rows_per_part:\n            if book:\n                book.save(written[-1][0])\n            part += 1\n            book = Workbook(write_only=True)\n            sheet_out = book.create_sheet(ws.title)\n            sheet_out.append(header)\n            written.append([out_dir \u002F f\"{Path(path).stem}_part_{part:02d}.xlsx\", 0])\n            count = 0\n        sheet_out.append(transform(sheet_out, row) if transform else row)\n        count += 1\n        written[-1][1] += 1\n    if book:\n        book.save(written[-1][0])\n    src.close()\n    return written\n\nimport time\nt = time.perf_counter()\nresult = split_stream(\"transactions.xlsx\", ROWS, \"parts_stream\")\nprint([(p.name, n) for p, n in result], f\"{time.perf_counter() - t:.1f}s\")\n",[32,607,608,620,624,651,661,674,693,719,738,751,773,786,800,808,828,840,860,871,877,916,927,943,953,974,982,998,1004,1013,1018,1026,1037,1061],{"__ignoreMap":30},[35,609,610,612,615,617],{"class":37,"line":38},[35,611,232],{"class":221},[35,613,614],{"class":225}," openpyxl ",[35,616,222],{"class":221},[35,618,619],{"class":225}," Workbook, load_workbook\n",[35,621,622],{"class":37,"line":229},[35,623,261],{"emptyLinePlaceholder":260},[35,625,626,629,633,636,638,641,644,646,648],{"class":37,"line":243},[35,627,628],{"class":221},"def",[35,630,632],{"class":631},"s_Opv"," split_stream",[35,634,635],{"class":225},"(path, rows_per_part, out_dir, sheet",[35,637,283],{"class":221},[35,639,640],{"class":267},"None",[35,642,643],{"class":225},", transform",[35,645,283],{"class":221},[35,647,640],{"class":267},[35,649,650],{"class":225},"):\n",[35,652,653,656,658],{"class":37,"line":257},[35,654,655],{"class":225},"    out_dir ",[35,657,283],{"class":221},[35,659,660],{"class":225}," Path(out_dir)\n",[35,662,663,666,668,670,672],{"class":37,"line":264},[35,664,665],{"class":225},"    out_dir.mkdir(",[35,667,374],{"class":295},[35,669,283],{"class":221},[35,671,379],{"class":267},[35,673,349],{"class":225},[35,675,676,679,681,684,687,689,691],{"class":37,"line":277},[35,677,678],{"class":225},"    src ",[35,680,283],{"class":221},[35,682,683],{"class":225}," load_workbook(path, ",[35,685,686],{"class":295},"read_only",[35,688,283],{"class":221},[35,690,379],{"class":267},[35,692,349],{"class":225},[35,694,695,698,700,703,706,709,712,715,717],{"class":37,"line":326},[35,696,697],{"class":225},"    ws ",[35,699,283],{"class":221},[35,701,702],{"class":225}," src[sheet] ",[35,704,705],{"class":221},"if",[35,707,708],{"class":225}," sheet ",[35,710,711],{"class":221},"else",[35,713,714],{"class":225}," src.worksheets[",[35,716,82],{"class":267},[35,718,434],{"class":225},[35,720,721,724,726,729,732,734,736],{"class":37,"line":352},[35,722,723],{"class":225},"    rows ",[35,725,283],{"class":221},[35,727,728],{"class":225}," ws.iter_rows(",[35,730,731],{"class":295},"values_only",[35,733,283],{"class":221},[35,735,379],{"class":267},[35,737,349],{"class":225},[35,739,740,743,745,748],{"class":37,"line":368},[35,741,742],{"class":225},"    header ",[35,744,283],{"class":221},[35,746,747],{"class":267}," next",[35,749,750],{"class":225},"(rows)\n",[35,752,753,756,758,761,763,766,768,770],{"class":37,"line":384},[35,754,755],{"class":225},"    written, part, count, book, sheet_out ",[35,757,283],{"class":221},[35,759,760],{"class":225}," [], ",[35,762,82],{"class":267},[35,764,765],{"class":225},", rows_per_part, ",[35,767,640],{"class":267},[35,769,292],{"class":225},[35,771,772],{"class":267},"None\n",[35,774,775,778,781,783],{"class":37,"line":402},[35,776,777],{"class":221},"    for",[35,779,780],{"class":225}," row ",[35,782,393],{"class":221},[35,784,785],{"class":225}," rows:\n",[35,787,788,791,794,797],{"class":37,"line":437},[35,789,790],{"class":221},"        if",[35,792,793],{"class":225}," count ",[35,795,796],{"class":221},"==",[35,798,799],{"class":225}," rows_per_part:\n",[35,801,802,805],{"class":37,"line":482},[35,803,804],{"class":221},"            if",[35,806,807],{"class":225}," book:\n",[35,809,811,814,817,820,823,825],{"class":37,"line":810},14,[35,812,813],{"class":225},"                book.save(written[",[35,815,816],{"class":221},"-",[35,818,819],{"class":267},"1",[35,821,822],{"class":225},"][",[35,824,82],{"class":267},[35,826,827],{"class":225},"])\n",[35,829,831,834,837],{"class":37,"line":830},15,[35,832,833],{"class":225},"            part ",[35,835,836],{"class":221},"+=",[35,838,839],{"class":267}," 1\n",[35,841,843,846,848,851,854,856,858],{"class":37,"line":842},16,[35,844,845],{"class":225},"            book ",[35,847,283],{"class":221},[35,849,850],{"class":225}," Workbook(",[35,852,853],{"class":295},"write_only",[35,855,283],{"class":221},[35,857,379],{"class":267},[35,859,349],{"class":225},[35,861,863,866,868],{"class":37,"line":862},17,[35,864,865],{"class":225},"            sheet_out ",[35,867,283],{"class":221},[35,869,870],{"class":225}," book.create_sheet(ws.title)\n",[35,872,874],{"class":37,"line":873},18,[35,875,876],{"class":225},"            sheet_out.append(header)\n",[35,878,880,883,885,887,889,891,894,896,899,901,904,906,908,910,912,914],{"class":37,"line":879},19,[35,881,882],{"class":225},"            written.append([out_dir ",[35,884,343],{"class":221},[35,886,445],{"class":221},[35,888,546],{"class":45},[35,890,311],{"class":451},[35,892,893],{"class":225},"Path(path).stem",[35,895,464],{"class":451},[35,897,898],{"class":45},"_part_",[35,900,311],{"class":451},[35,902,903],{"class":225},"part",[35,905,461],{"class":221},[35,907,464],{"class":451},[35,909,467],{"class":45},[35,911,292],{"class":225},[35,913,82],{"class":267},[35,915,827],{"class":225},[35,917,919,922,924],{"class":37,"line":918},20,[35,920,921],{"class":225},"            count ",[35,923,283],{"class":221},[35,925,926],{"class":267}," 0\n",[35,928,930,933,935,938,940],{"class":37,"line":929},21,[35,931,932],{"class":225},"        sheet_out.append(transform(sheet_out, row) ",[35,934,705],{"class":221},[35,936,937],{"class":225}," transform ",[35,939,711],{"class":221},[35,941,942],{"class":225}," row)\n",[35,944,946,949,951],{"class":37,"line":945},22,[35,947,948],{"class":225},"        count ",[35,950,836],{"class":221},[35,952,839],{"class":267},[35,954,956,959,961,963,965,967,970,972],{"class":37,"line":955},23,[35,957,958],{"class":225},"        written[",[35,960,816],{"class":221},[35,962,819],{"class":267},[35,964,822],{"class":225},[35,966,819],{"class":267},[35,968,969],{"class":225},"] ",[35,971,836],{"class":221},[35,973,839],{"class":267},[35,975,977,980],{"class":37,"line":976},24,[35,978,979],{"class":221},"    if",[35,981,807],{"class":225},[35,983,985,988,990,992,994,996],{"class":37,"line":984},25,[35,986,987],{"class":225},"        book.save(written[",[35,989,816],{"class":221},[35,991,819],{"class":267},[35,993,822],{"class":225},[35,995,82],{"class":267},[35,997,827],{"class":225},[35,999,1001],{"class":37,"line":1000},26,[35,1002,1003],{"class":225},"    src.close()\n",[35,1005,1007,1010],{"class":37,"line":1006},27,[35,1008,1009],{"class":221},"    return",[35,1011,1012],{"class":225}," written\n",[35,1014,1016],{"class":37,"line":1015},28,[35,1017,261],{"emptyLinePlaceholder":260},[35,1019,1021,1023],{"class":37,"line":1020},29,[35,1022,222],{"class":221},[35,1024,1025],{"class":225}," time\n",[35,1027,1029,1032,1034],{"class":37,"line":1028},30,[35,1030,1031],{"class":225},"t ",[35,1033,283],{"class":221},[35,1035,1036],{"class":225}," time.perf_counter()\n",[35,1038,1040,1043,1045,1048,1050,1052,1054,1056,1059],{"class":37,"line":1039},31,[35,1041,1042],{"class":225},"result ",[35,1044,283],{"class":221},[35,1046,1047],{"class":225}," split_stream(",[35,1049,289],{"class":45},[35,1051,292],{"class":225},[35,1053,268],{"class":267},[35,1055,292],{"class":225},[35,1057,1058],{"class":45},"\"parts_stream\"",[35,1060,349],{"class":225},[35,1062,1064,1067,1070,1072,1075,1077,1080,1082,1084,1086,1089,1091,1094,1097,1099,1102],{"class":37,"line":1063},32,[35,1065,1066],{"class":267},"print",[35,1068,1069],{"class":225},"([(p.name, n) ",[35,1071,387],{"class":221},[35,1073,1074],{"class":225}," p, n ",[35,1076,393],{"class":221},[35,1078,1079],{"class":225}," result], ",[35,1081,491],{"class":221},[35,1083,546],{"class":45},[35,1085,311],{"class":451},[35,1087,1088],{"class":225},"time.perf_counter() ",[35,1090,816],{"class":221},[35,1092,1093],{"class":225}," t",[35,1095,1096],{"class":221},":.1f",[35,1098,464],{"class":451},[35,1100,1101],{"class":45},"s\"",[35,1103,349],{"class":225},[10,1105,1106,1107,1111,1112,1116],{},"A new output workbook starts whenever the current one reaches the limit: the previous part is saved, a fresh write-only workbook is created, and the header is written first. Starting the counter at the limit makes the very first row open part one without a special case. Write-only sheets flush rows to a temporary file as they are appended, so memory stays roughly constant however large the source is. ",[14,1108,1110],{"href":1109},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fspeed-up-openpyxl-with-read-only-mode\u002F","Speed Up openpyxl with Read-Only Mode"," and ",[14,1113,1115],{"href":1114},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fwrite-large-dataframes-to-excel-with-write-only-mode\u002F","Write Large DataFrames to Excel with Write-Only Mode"," describe each mode in detail.",[60,1118,69,1123,69,1126,69,1129,69,1132,69,1136,69,1141,69,1144,69,1149,69,1152,69,1156,69,1161,69,1163,69,1165,69,1170,69,1173,69,1180,69,1186,69,1189,69,1192,69,1197,69,1201,69,1206,69,1209,69,1214,69,1219,69,1222,69,1225,69,1230],{"viewBox":1119,"role":63,"ariaLabelledBy":1120,"xmlns":67,"style":68},"4 0 752 292",[1121,1122],"sp-stream-t","sp-stream-d",[71,1124,1125],{"id":1121},"How the streaming split moves rows",[75,1127,1128],{"id":1122},"A sequence diagram. The read-only source yields the header and then rows one at a time; the splitter appends each row to the current write-only part, saves the part when it is full and opens the next one with the header repeated.",[79,1130],{"x":81,"y":82,"width":83,"height":1131,"fill":85},"292",[79,1133],{"x":88,"y":121,"width":1134,"height":1135,"rx":92,"fill":124,"stroke":125,"style":95},"180.0","48.0",[108,1137,1140],{"x":1138,"y":1139,"style":137},"110.0","44.5","source (read-only)",[79,1142],{"x":1143,"y":121,"width":1134,"height":1135,"rx":92,"fill":93,"stroke":94,"style":95},"290.0",[108,1145,1148],{"x":1146,"y":1139,"style":1147},"380.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","splitter",[79,1150],{"x":1151,"y":121,"width":1134,"height":1135,"rx":92,"fill":93,"stroke":94,"style":95},"560.0",[108,1153,1155],{"x":1154,"y":1139,"style":1147},"650.0","part file (write-only)",[37,1157],{"x1":1138,"y1":1158,"x2":1138,"y2":1159,"stroke":94,"style":1160},"64.0","276.0","stroke-width:1.5px;stroke-dasharray:5 4",[37,1162],{"x1":1146,"y1":1158,"x2":1146,"y2":1159,"stroke":94,"style":1160},[37,1164],{"x1":1154,"y1":1158,"x2":1154,"y2":1159,"stroke":94,"style":1160},[37,1166],{"x1":1167,"y1":1168,"x2":1169,"y2":1168,"stroke":125,"style":95},"115.0","104.0","371.0",[148,1171],{"points":1172,"fill":151},"375.0,104.0 366.0,109.0 366.0,99.0",[79,1174],{"x":1175,"y":136,"width":1176,"height":1177,"rx":1178,"fill":85,"stroke":94,"style":1179},"196.5","97.0","21.0","10","stroke-width:1.2px",[108,1181,1185],{"x":1182,"y":1183,"style":1184},"245.0","89.5","font-size:11.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","1. header row",[37,1187],{"x1":1167,"y1":1188,"x2":1169,"y2":1188,"stroke":125,"style":95},"152.0",[148,1190],{"points":1191,"fill":151},"375.0,152.0 366.0,157.0 366.0,147.0",[79,1193],{"x":1194,"y":1195,"width":1196,"height":1177,"rx":1178,"fill":85,"stroke":94,"style":1179},"195.5","123.0","99.0",[108,1198,1200],{"x":1182,"y":1199,"style":1184},"137.5","2. next row …",[37,1202],{"x1":1203,"y1":1204,"x2":1205,"y2":1204,"stroke":125,"style":95},"385.0","200.0","641.0",[148,1207],{"points":1208,"fill":151},"645.0,200.0 636.0,205.0 636.0,195.0",[79,1210],{"x":1211,"y":1212,"width":1213,"height":1177,"rx":1178,"fill":85,"stroke":94,"style":1179},"463.7","171.0","102.7",[108,1215,1218],{"x":1216,"y":1217,"style":1184},"515.0","185.5","3. append row",[37,1220],{"x1":1203,"y1":1221,"x2":1205,"y2":1221,"stroke":125,"style":95},"248.0",[148,1223],{"points":1224,"fill":151},"645.0,248.0 636.0,253.0 636.0,243.0",[79,1226],{"x":1227,"y":1228,"width":1229,"height":1177,"rx":1178,"fill":85,"stroke":94,"style":1179},"398.0","219.0","233.9",[108,1231,1233],{"x":1216,"y":1232,"style":1184},"233.5","4. full: save, open next, write header",[20,1235,1237],{"id":1236},"step-3-keep-dates-as-dates","Step 3 — Keep dates as dates",[10,1239,1240,1243,1244,1247],{},[32,1241,1242],{},"values_only=True"," returns Python ",[32,1245,1246],{},"datetime"," objects for date cells, and a write-only workbook writes them as dates — but with openpyxl's default number format, which shows the time as well. Give the date column a format by writing styled cells for it:",[25,1249,1251],{"className":212,"code":1250,"language":214,"meta":30,"style":30},"from openpyxl.cell import WriteOnlyCell\n\ndef first_date_cell(path):\n    wb = load_workbook(path, read_only=True)\n    cell = next(wb.worksheets[0].iter_rows(min_row=2, max_row=2))[1]\n    info = (type(cell.value).__name__, cell.number_format)\n    wb.close()\n    return info\n\nprint(\"before:\", first_date_cell(result[0][0]))\n\ndef styled(sheet, row, date_cols=(1,)):\n    cells = []\n    for i, v in enumerate(row):\n        if i in date_cols and v is not None:\n            c = WriteOnlyCell(sheet, value=v)\n            c.number_format = \"yyyy-mm-dd\"\n            cells.append(c)\n        else:\n            cells.append(v)\n    return cells\n\nresult = split_stream(\"transactions.xlsx\", ROWS, \"parts_stream\", transform=styled)\nprint(\"after: \", first_date_cell(result[0][0]))\n",[32,1252,1253,1265,1269,1279,1296,1337,1359,1364,1371,1375,1396,1400,1419,1429,1444,1473,1491,1501,1506,1513,1518,1525,1529,1557],{"__ignoreMap":30},[35,1254,1255,1257,1260,1262],{"class":37,"line":38},[35,1256,232],{"class":221},[35,1258,1259],{"class":225}," openpyxl.cell ",[35,1261,222],{"class":221},[35,1263,1264],{"class":225}," WriteOnlyCell\n",[35,1266,1267],{"class":37,"line":229},[35,1268,261],{"emptyLinePlaceholder":260},[35,1270,1271,1273,1276],{"class":37,"line":243},[35,1272,628],{"class":221},[35,1274,1275],{"class":631}," first_date_cell",[35,1277,1278],{"class":225},"(path):\n",[35,1280,1281,1284,1286,1288,1290,1292,1294],{"class":37,"line":257},[35,1282,1283],{"class":225},"    wb ",[35,1285,283],{"class":221},[35,1287,683],{"class":225},[35,1289,686],{"class":295},[35,1291,283],{"class":221},[35,1293,379],{"class":267},[35,1295,349],{"class":225},[35,1297,1298,1301,1303,1305,1308,1310,1313,1316,1318,1321,1323,1326,1328,1330,1333,1335],{"class":37,"line":264},[35,1299,1300],{"class":225},"    cell ",[35,1302,283],{"class":221},[35,1304,747],{"class":267},[35,1306,1307],{"class":225},"(wb.worksheets[",[35,1309,82],{"class":267},[35,1311,1312],{"class":225},"].iter_rows(",[35,1314,1315],{"class":295},"min_row",[35,1317,283],{"class":221},[35,1319,1320],{"class":267},"2",[35,1322,292],{"class":225},[35,1324,1325],{"class":295},"max_row",[35,1327,283],{"class":221},[35,1329,1320],{"class":267},[35,1331,1332],{"class":225},"))[",[35,1334,819],{"class":267},[35,1336,434],{"class":225},[35,1338,1339,1342,1344,1347,1350,1353,1356],{"class":37,"line":277},[35,1340,1341],{"class":225},"    info ",[35,1343,283],{"class":221},[35,1345,1346],{"class":225}," (",[35,1348,1349],{"class":267},"type",[35,1351,1352],{"class":225},"(cell.value).",[35,1354,1355],{"class":267},"__name__",[35,1357,1358],{"class":225},", cell.number_format)\n",[35,1360,1361],{"class":37,"line":326},[35,1362,1363],{"class":225},"    wb.close()\n",[35,1365,1366,1368],{"class":37,"line":352},[35,1367,1009],{"class":221},[35,1369,1370],{"class":225}," info\n",[35,1372,1373],{"class":37,"line":368},[35,1374,261],{"emptyLinePlaceholder":260},[35,1376,1377,1379,1381,1384,1387,1389,1391,1393],{"class":37,"line":384},[35,1378,1066],{"class":267},[35,1380,488],{"class":225},[35,1382,1383],{"class":45},"\"before:\"",[35,1385,1386],{"class":225},", first_date_cell(result[",[35,1388,82],{"class":267},[35,1390,822],{"class":225},[35,1392,82],{"class":267},[35,1394,1395],{"class":225},"]))\n",[35,1397,1398],{"class":37,"line":402},[35,1399,261],{"emptyLinePlaceholder":260},[35,1401,1402,1404,1407,1410,1412,1414,1416],{"class":37,"line":437},[35,1403,628],{"class":221},[35,1405,1406],{"class":631}," styled",[35,1408,1409],{"class":225},"(sheet, row, date_cols",[35,1411,283],{"class":221},[35,1413,488],{"class":225},[35,1415,819],{"class":267},[35,1417,1418],{"class":225},",)):\n",[35,1420,1421,1424,1426],{"class":37,"line":482},[35,1422,1423],{"class":225},"    cells ",[35,1425,283],{"class":221},[35,1427,1428],{"class":225}," []\n",[35,1430,1431,1433,1436,1438,1441],{"class":37,"line":810},[35,1432,777],{"class":221},[35,1434,1435],{"class":225}," i, v ",[35,1437,393],{"class":221},[35,1439,1440],{"class":267}," enumerate",[35,1442,1443],{"class":225},"(row):\n",[35,1445,1446,1448,1450,1452,1455,1458,1461,1464,1467,1470],{"class":37,"line":830},[35,1447,790],{"class":221},[35,1449,390],{"class":225},[35,1451,393],{"class":221},[35,1453,1454],{"class":225}," date_cols ",[35,1456,1457],{"class":221},"and",[35,1459,1460],{"class":225}," v ",[35,1462,1463],{"class":221},"is",[35,1465,1466],{"class":221}," not",[35,1468,1469],{"class":267}," None",[35,1471,1472],{"class":225},":\n",[35,1474,1475,1478,1480,1483,1486,1488],{"class":37,"line":842},[35,1476,1477],{"class":225},"            c ",[35,1479,283],{"class":221},[35,1481,1482],{"class":225}," WriteOnlyCell(sheet, ",[35,1484,1485],{"class":295},"value",[35,1487,283],{"class":221},[35,1489,1490],{"class":225},"v)\n",[35,1492,1493,1496,1498],{"class":37,"line":862},[35,1494,1495],{"class":225},"            c.number_format ",[35,1497,283],{"class":221},[35,1499,1500],{"class":45}," \"yyyy-mm-dd\"\n",[35,1502,1503],{"class":37,"line":873},[35,1504,1505],{"class":225},"            cells.append(c)\n",[35,1507,1508,1511],{"class":37,"line":879},[35,1509,1510],{"class":221},"        else",[35,1512,1472],{"class":225},[35,1514,1515],{"class":37,"line":918},[35,1516,1517],{"class":225},"            cells.append(v)\n",[35,1519,1520,1522],{"class":37,"line":929},[35,1521,1009],{"class":221},[35,1523,1524],{"class":225}," cells\n",[35,1526,1527],{"class":37,"line":945},[35,1528,261],{"emptyLinePlaceholder":260},[35,1530,1531,1533,1535,1537,1539,1541,1543,1545,1547,1549,1552,1554],{"class":37,"line":955},[35,1532,1042],{"class":225},[35,1534,283],{"class":221},[35,1536,1047],{"class":225},[35,1538,289],{"class":45},[35,1540,292],{"class":225},[35,1542,268],{"class":267},[35,1544,292],{"class":225},[35,1546,1058],{"class":45},[35,1548,292],{"class":225},[35,1550,1551],{"class":295},"transform",[35,1553,283],{"class":221},[35,1555,1556],{"class":225},"styled)\n",[35,1558,1559,1561,1563,1566,1568,1570,1572,1574],{"class":37,"line":976},[35,1560,1066],{"class":267},[35,1562,488],{"class":225},[35,1564,1565],{"class":45},"\"after: \"",[35,1567,1386],{"class":225},[35,1569,82],{"class":267},[35,1571,822],{"class":225},[35,1573,82],{"class":267},[35,1575,1395],{"class":225},[10,1577,1578,1579,1582],{},"A ",[32,1580,1581],{},"WriteOnlyCell"," carries a style in write-only mode, where ordinary cells cannot be styled after they are written. Most import tools read the underlying date value and do not care about the display format, but anyone opening a part in Excel will.",[20,1584,1586],{"id":1585},"step-4-check-the-parts-against-the-source","Step 4 — Check the parts against the source",[10,1588,1589],{},"Splitting is exactly the kind of job where an off-by-one error loses a row at every boundary without anyone noticing. Count and compare — rows, and a key that should appear exactly once:",[25,1591,1593],{"className":212,"code":1592,"language":214,"meta":30,"style":30},"source_ids = pd.read_excel(\"transactions.xlsx\", usecols=[\"TransactionID\"])[\"TransactionID\"]\npart_ids = pd.concat([pd.read_excel(p, usecols=[\"TransactionID\"])[\"TransactionID\"] for p, _ in result])\nprint(\"rows:\", len(source_ids), \"->\", len(part_ids))\nprint(\"same IDs:\", sorted(source_ids) == sorted(part_ids), \"| duplicates:\", part_ids.duplicated().sum())\nprint(\"max rows per part:\", max(n for _, n in result))\n",[32,1594,1595,1626,1660,1686,1717],{"__ignoreMap":30},[35,1596,1597,1600,1602,1604,1606,1608,1611,1613,1616,1619,1622,1624],{"class":37,"line":38},[35,1598,1599],{"class":225},"source_ids ",[35,1601,283],{"class":221},[35,1603,286],{"class":225},[35,1605,289],{"class":45},[35,1607,292],{"class":225},[35,1609,1610],{"class":295},"usecols",[35,1612,283],{"class":221},[35,1614,1615],{"class":225},"[",[35,1617,1618],{"class":45},"\"TransactionID\"",[35,1620,1621],{"class":225},"])[",[35,1623,1618],{"class":45},[35,1625,434],{"class":225},[35,1627,1628,1631,1633,1636,1638,1640,1642,1644,1646,1648,1650,1652,1655,1657],{"class":37,"line":229},[35,1629,1630],{"class":225},"part_ids ",[35,1632,283],{"class":221},[35,1634,1635],{"class":225}," pd.concat([pd.read_excel(p, ",[35,1637,1610],{"class":295},[35,1639,283],{"class":221},[35,1641,1615],{"class":225},[35,1643,1618],{"class":45},[35,1645,1621],{"class":225},[35,1647,1618],{"class":45},[35,1649,969],{"class":225},[35,1651,387],{"class":221},[35,1653,1654],{"class":225}," p, _ ",[35,1656,393],{"class":221},[35,1658,1659],{"class":225}," result])\n",[35,1661,1662,1664,1666,1669,1671,1673,1676,1679,1681,1683],{"class":37,"line":243},[35,1663,1066],{"class":267},[35,1665,488],{"class":225},[35,1667,1668],{"class":45},"\"rows:\"",[35,1670,292],{"class":225},[35,1672,337],{"class":267},[35,1674,1675],{"class":225},"(source_ids), ",[35,1677,1678],{"class":45},"\"->\"",[35,1680,292],{"class":225},[35,1682,337],{"class":267},[35,1684,1685],{"class":225},"(part_ids))\n",[35,1687,1688,1690,1692,1695,1697,1700,1703,1705,1708,1711,1714],{"class":37,"line":257},[35,1689,1066],{"class":267},[35,1691,488],{"class":225},[35,1693,1694],{"class":45},"\"same IDs:\"",[35,1696,292],{"class":225},[35,1698,1699],{"class":267},"sorted",[35,1701,1702],{"class":225},"(source_ids) ",[35,1704,796],{"class":221},[35,1706,1707],{"class":267}," sorted",[35,1709,1710],{"class":225},"(part_ids), ",[35,1712,1713],{"class":45},"\"| duplicates:\"",[35,1715,1716],{"class":225},", part_ids.duplicated().sum())\n",[35,1718,1719,1721,1723,1726,1728,1731,1734,1736,1739,1741],{"class":37,"line":264},[35,1720,1066],{"class":267},[35,1722,488],{"class":225},[35,1724,1725],{"class":45},"\"max rows per part:\"",[35,1727,292],{"class":225},[35,1729,1730],{"class":267},"max",[35,1732,1733],{"class":225},"(n ",[35,1735,387],{"class":221},[35,1737,1738],{"class":225}," _, n ",[35,1740,393],{"class":221},[35,1742,1743],{"class":225}," result))\n",[10,1745,1746,1747,1749,1750,18],{},"Same count, same IDs, no duplicates, and no part over the limit: the split is correct. Reading only the ID column with ",[32,1748,1610],{}," keeps the check cheap. For files without a unique ID, compare row counts and the sum of a numeric column instead — see ",[14,1751,1753],{"href":1752},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Freconcile-excel-totals-with-control-totals\u002F","Reconcile Excel Totals with Control Totals",[20,1755,1757],{"id":1756},"splitting-by-size-instead-of-rows","Splitting by size instead of rows",[10,1759,1760,1761,1764],{},"An upload limit is often in megabytes, not rows. The size of an ",[32,1762,1763],{},".xlsx"," depends on how well its XML compresses, so it cannot be computed exactly in advance. The practical approach: split one part with a guessed row count, measure the file size, and scale — here 50,000 rows made a 1.6 MB file, so for a 5 MB limit about 140,000 rows per part leaves a safety margin. Re-check the largest part afterwards, since text-heavy rows compress less well than numeric ones.",[60,1766,69,1772,69,1775,69,1778,69,1782,69,1788,69,1794,69,1799,69,1804,69,1807,69,1811,69,1814,69,1817,69,1820,69,1824,69,1827,69,1832,69,1837],{"viewBox":1767,"role":63,"ariaLabelledBy":1768,"xmlns":67,"style":1771},"4 0 733 184",[1769,1770],"sp-sizes-t","sp-sizes-d","width:100%;max-width:733px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[71,1773,1774],{"id":1769},"Rows per part against the part file sizes",[75,1776,1777],{"id":1770},"Horizontal bars of the three part files. The two 50,000-row parts are about 1.6 megabytes each and the 20,000-row part about 0.65 megabytes.",[79,1779],{"x":81,"y":82,"width":1780,"height":1781,"fill":85},"733","184",[79,1783],{"x":88,"y":121,"width":1784,"height":1785,"rx":1786,"fill":93,"stroke":94,"style":1787},"170.5","28.0","7","stroke-width:1px",[108,1789,1793],{"x":1790,"y":1791,"style":1792},"105.3","34.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","part 01 (50,000 rows)",[79,1795],{"x":1796,"y":121,"width":1797,"height":1785,"rx":1786,"fill":124,"stroke":125,"style":1798},"200.5","468.8","stroke-width:1.5px",[108,1800,1803],{"x":1801,"y":1791,"style":1802},"677.3","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","1.6 MB",[79,1805],{"x":88,"y":1806,"width":1784,"height":1785,"rx":1786,"fill":93,"stroke":94,"style":1787},"56.0",[108,1808,1810],{"x":1790,"y":1809,"style":1792},"74.5","part 02 (50,000 rows)",[79,1812],{"x":1796,"y":1806,"width":1813,"height":1785,"rx":1786,"fill":124,"stroke":125,"style":1798},"471.7",[108,1815,1803],{"x":1816,"y":1809,"style":1802},"680.2",[79,1818],{"x":88,"y":1819,"width":1784,"height":1785,"rx":1786,"fill":93,"stroke":94,"style":1787},"96.0",[108,1821,1823],{"x":1790,"y":1822,"style":1792},"114.5","part 03 (20,000 rows)",[79,1825],{"x":1796,"y":1819,"width":1826,"height":1785,"rx":1786,"fill":176,"stroke":177,"style":1798},"188.1",[108,1828,1831],{"x":1829,"y":1822,"style":1830},"396.6","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","0.65 MB",[108,1833,1836],{"x":1146,"y":1834,"style":1835},"146.0","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","Sizes are roughly proportional to rows for uniform data; measure one part before choosing a row count for a",[108,1838,1840],{"x":1146,"y":1839,"style":1835},"164.0","size limit.",[20,1842,1844],{"id":1843},"performance-notes","Performance notes",[10,1846,1847,1848,1852],{},"On this 120,000-row file the streaming split took about five seconds, nearly all of it spent parsing the source XML and writing the output XML; the pandas version takes a similar time because it uses the same parser underneath. What streaming changes is memory, not speed: its memory use stays roughly constant however long the sheet is — one row in flight plus buffers — while the pandas version holds the whole sheet at once and grows with the file. If the parts go to a system that also accepts CSV, writing CSV parts is several times faster and produces smaller files — ",[14,1849,1851],{"href":1850},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fconvert-excel-to-csv-with-python\u002F","Convert Excel to CSV with Python"," covers that route, including the encoding to choose.",[10,1854,1855],{},"Splitting also parallelises well when there are many source files rather than one big one: run one split per file in a process pool. For a single huge sheet it does not, because the read-only reader has to parse the sheet from the top to reach any given row.",[20,1857,1859],{"id":1858},"common-pitfalls","Common pitfalls",[1861,1862,1863,1871,1882,1900,1910],"ul",{},[1864,1865,1866,1870],"li",{},[1867,1868,1869],"strong",{},"Forgetting the header in later parts."," Each part needs its own header row; pandas adds it, a streaming splitter must write it.",[1864,1872,1873,1876,1877,572,1879,1881],{},[1867,1874,1875],{},"Unpadded numbers."," ",[32,1878,564],{},[32,1880,575],{},"; zero-pad to the number of digits you need.",[1864,1883,1884,1887,1888,1891,1892,1895,1896,1899],{},[1867,1885,1886],{},"Leading zeros in IDs."," Reading with pandas without ",[32,1889,1890],{},"dtype=str"," turns ",[32,1893,1894],{},"\"00123\""," into ",[32,1897,1898],{},"123","; the streaming version copies values as stored.",[1864,1901,1902,1905,1906,1909],{},[1867,1903,1904],{},"Not closing the read-only source."," It keeps the file open until ",[32,1907,1908],{},"close()",", which locks it on Windows.",[1864,1911,1912,1915],{},[1867,1913,1914],{},"Trusting the split without checking."," Compare counts and keys; an off-by-one loses one row per part.",[20,1917,1919],{"id":1918},"conclusion","Conclusion",[10,1921,1922,1923,1925,1926,1928],{},"For files that load comfortably, read with pandas and write ",[32,1924,208],{}," slices of N rows to zero-padded file names. For large files, stream: openpyxl read-only to iterate the source, a write-only workbook per part with the header written first, and a new part whenever the current one is full — with ",[32,1927,1581],{}," for date formats. Then compare row counts and IDs between the parts and the source before sending anything on.",[20,1930,1932],{"id":1931},"frequently-asked-questions","Frequently asked questions",[10,1934,1935,1938,1939,1942],{},[1867,1936,1937],{},"How do I split an Excel file into multiple files with Python?","\nRead the sheet with pandas and write df.iloc",[35,1940,1941],{},"start:start + n"," slices with to_excel in a loop, one file per slice. For very large files, stream rows with openpyxl in read-only mode into write-only workbooks instead.",[10,1944,1945,1948],{},[1867,1946,1947],{},"How do I keep the header row in every split file?","\npandas writes the header with each to_excel call. With openpyxl, read the first row once and append it to each new output sheet before any data rows.",[10,1950,1951,1954],{},[1867,1952,1953],{},"How can I split a file that is too large to load into memory?","\nUse openpyxl: load_workbook(path, read_only=True) to iterate rows without loading the sheet, and Workbook(write_only=True) for each part, which writes rows to disk as they are appended.",[10,1956,1957,1960],{},[1867,1958,1959],{},"How do I split by file size rather than row count?","\nWrite one part, measure its size, and scale the row count to your size limit with a safety margin. The exact size depends on compression, so check the largest part afterwards.",[20,1962,1964],{"id":1963},"related","Related",[1861,1966,1967,1973,1980,1987],{},[1864,1968,1969,1970,1972],{},"Up one level: ",[14,1971,17],{"href":16}," — memory, speed and streaming.",[1864,1974,1975,1979],{},[14,1976,1978],{"href":1977},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fread-large-excel-file-in-chunks-with-pandas\u002F","Read a Large Excel File in Chunks with pandas"," — processing a big sheet part by part.",[1864,1981,1982,1986],{},[14,1983,1985],{"href":1984},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-multiple-excel-sheets-in-python\u002Fsplit-one-excel-sheet-into-multiple-files-by-value\u002F","Split One Excel Sheet into Multiple Files by Value"," — splitting by a column instead of a count.",[1864,1988,1989,1993],{},[14,1990,1992],{"href":1991},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fzip-large-excel-attachments-before-emailing\u002F","Zip Large Excel Attachments Before Emailing"," — the other answer to size limits.",[1995,1996,1997],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":30,"searchDepth":229,"depth":229,"links":1999},[2000,2001,2002,2003,2004,2005,2006,2007,2008,2009,2010],{"id":22,"depth":229,"text":23},{"id":201,"depth":229,"text":202},{"id":590,"depth":229,"text":591},{"id":1236,"depth":229,"text":1237},{"id":1585,"depth":229,"text":1586},{"id":1756,"depth":229,"text":1757},{"id":1843,"depth":229,"text":1844},{"id":1858,"depth":229,"text":1859},{"id":1918,"depth":229,"text":1919},{"id":1931,"depth":229,"text":1932},{"id":1963,"depth":229,"text":1964},"2026-09-29","Break a big workbook into smaller ones for an upload limit, an import tool or an email attachment: the simple pandas version, a streaming version with openpyxl read-only and write-only modes that keeps memory flat, the header repeated in every part, dates kept as dates, numbered file names, and a check that no row was lost or duplicated.","md",[2015,2017,2019,2021],{"q":1937,"a":2016},"Read the sheet with pandas and write df.iloc[start:start + n] slices with to_excel in a loop, one file per slice. For very large files, stream rows with openpyxl in read-only mode into write-only workbooks instead.",{"q":1947,"a":2018},"pandas writes the header with each to_excel call. With openpyxl, read the first row once and append it to each new output sheet before any data rows.",{"q":1953,"a":2020},"Use openpyxl: load_workbook(path, read_only=True) to iterate rows without loading the sheet, and Workbook(write_only=True) for each part, which writes rows to disk as they are appended.",{"q":1959,"a":2022},"Write one part, measure its size, and scale the row count to your size limit with a safety margin. The exact size depends on compression, so check the largest part afterwards.","split a large excel file into files of n rows",null,{"breadcrumb":2026},[2027,2029,2032],{"name":2028,"item":343},"Home",{"name":2030,"item":2031},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":17,"item":16},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows",{"title":2035,"description":2036},"Split a Large Excel File into Multiple Files by Row Count","Split an Excel sheet into chunks of N rows with Python: pandas iloc slices for moderate files, openpyxl read_only plus write_only streaming for large ones, headers and date formats in every part, zero-padded names and a row-count check.","split-a-large-excel-file-into-files-of-n-rows","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fsplit-a-large-excel-file-into-files-of-n-rows\u002Findex",[2040,2041],"large-files","sheets-and-files","how-to","jAI2eRJLw2NlhGcB4JOd3U45f7MgkpLdyCmC6AhfcNM",[2045,2049],{"title":2046,"path":2047,"stem":2048,"children":-1},"Speed Up openpyxl with read_only Mode","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fspeed-up-openpyxl-with-read-only-mode","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fspeed-up-openpyxl-with-read-only-mode\u002Findex",{"title":2050,"path":2051,"stem":2052,"children":-1},"Stream Excel Rows into a Database with openpyxl","\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fstream-excel-rows-into-a-database-with-openpyxl","advanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python\u002Fstream-excel-rows-into-a-database-with-openpyxl\u002Findex",[2054,2056],{"path":2055,"title":2030},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2057,"title":17},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-large-excel-files-in-python",[],[2060,2063,2066,2069,2072,2075],{"path":2061,"title":2062,"kind":2042},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine","Speed Up pandas Excel Reads with the calamine Engine",{"path":2064,"title":2065,"kind":2042},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fprofile-a-slow-excel-report-script","Profile a Slow Excel Report Script in Python",{"path":2067,"title":2068,"kind":2042},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory","Write a Million Rows to Excel with xlsxwriter",{"path":2070,"title":2071,"kind":2042},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fbenchmark-python-excel-read-and-write-speed","Benchmark Python Excel Read and Write Speed",{"path":2073,"title":2074,"kind":2042},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fexport-a-large-database-table-to-excel-in-chunks","Export a Large Database Table to Excel in Chunks",{"path":2076,"title":2077,"kind":2042},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmoving-data-between-excel-and-databases\u002Fquery-excel-files-with-sql-using-duckdb","Query Excel Files with SQL Using DuckDB",1790972106829]