[{"data":1,"prerenderedAt":1257},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter":1250},{"id":4,"title":5,"body":6,"description":1242,"extension":1243,"meta":1244,"navigation":307,"path":1245,"seo":1246,"stem":1248,"__hash__":1249},"docs\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter\u002Findex.md","openpyxl vs xlsxwriter vs pandas.ExcelWriter",{"type":7,"value":8,"toc":1228},"minimark",[9,38,43,46,77,81,101,135,139,272,276,279,419,423,430,650,654,657,841,845,860,1025,1029,1109,1120,1124,1138,1142,1150,1165,1171,1187,1191,1200,1204,1224],[10,11,12,13,17,18,22,23,26,27,31,32,37],"p",{},"The three names look like competing libraries, but they sit at different layers. ",[14,15,16],"code",{},"pandas.ExcelWriter"," is not an engine at all — it's a thin wrapper that delegates to one. The real engines are ",[19,20,21],"strong",{},"openpyxl"," and ",[19,24,25],{},"xlsxwriter",", and they have opposite strengths: openpyxl can read ",[28,29,30],"em",{},"and"," write existing files and preserves styles, while xlsxwriter is write-only but faster and richer at formatting and charts. This guide, part of ",[33,34,36],"a",{"href":35},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002F","Writing DataFrames to Excel with Pandas",", clears up the relationship, gives you a decision table, and writes the same DataFrame three ways.",[39,40,42],"h2",{"id":41},"prerequisites","Prerequisites",[10,44,45],{},"Install pandas and both engines:",[47,48,53],"pre",{"className":49,"code":50,"language":51,"meta":52,"style":52},"language-bash shiki shiki-themes github-light github-dark","pip install pandas openpyxl xlsxwriter\n","bash","",[14,54,55],{"__ignoreMap":52},[56,57,60,64,68,71,74],"span",{"class":58,"line":59},"line",1,[56,61,63],{"class":62},"sScJk","pip",[56,65,67],{"class":66},"sZZnC"," install",[56,69,70],{"class":66}," pandas",[56,72,73],{"class":66}," openpyxl",[56,75,76],{"class":66}," xlsxwriter\n",[39,78,80],{"id":79},"how-the-pieces-relate","How the pieces relate",[10,82,83,22,86,89,90,92,93,96,97,100],{},[14,84,85],{},"df.to_excel(path)",[14,87,88],{},"pd.ExcelWriter(path)"," both produce a file by handing your data to an engine. When you don't name one, pandas picks a default (",[14,91,21],{}," for ",[14,94,95],{},".xlsx","). You choose explicitly with ",[14,98,99],{},"engine=",":",[102,103,104,113,118],"ul",{},[105,106,107,109,110,112],"li",{},[19,108,21],{}," — read + write. Opens an existing ",[14,111,95],{},", edits cells in place, appends sheets, and keeps the styles already in the file. This is the only engine that can modify a workbook you already have.",[105,114,115,117],{},[19,116,25],{}," — write only. It builds a brand-new file fast and exposes the richest formatting, conditional formats, and native charts. It cannot open or read an existing workbook.",[105,119,120,122,123,126,127,130,131,134],{},[19,121,16],{}," — the wrapper. It exposes ",[14,124,125],{},"mode=\"a\""," (append) and a ",[14,128,129],{},"book","\u002F",[14,132,133],{},"sheets"," handle so you can reach the underlying engine object for formatting.",[39,136,138],{"id":137},"decision-table","Decision table",[140,141,142,162],"table",{},[143,144,145],"thead",{},[146,147,148,152,154,156],"tr",{},[149,150,151],"th",{},"Need",[149,153,21],{},[149,155,25],{},[149,157,158,159],{},"plain ",[14,160,161],{},"to_excel",[163,164,165,180,191,206,220,232,246,259],"tbody",{},[146,166,167,171,174,177],{},[168,169,170],"td",{},"Read an existing file",[168,172,173],{},"Yes",[168,175,176],{},"No",[168,178,179],{},"n\u002Fa (write-only)",[146,181,182,185,187,189],{},[168,183,184],{},"Edit a file in place",[168,186,173],{},[168,188,176],{},[168,190,176],{},[146,192,193,199,201,203],{},[168,194,195,196,198],{},"Append a sheet (",[14,197,125],{},")",[168,200,173],{},[168,202,176],{},[168,204,205],{},"Uses openpyxl",[146,207,208,211,214,217],{},[168,209,210],{},"Native charts",[168,212,213],{},"Limited",[168,215,216],{},"Yes (strong)",[168,218,219],{},"Whatever engine you pass",[146,221,222,225,227,229],{},[168,223,224],{},"Conditional formatting",[168,226,213],{},[168,228,173],{},[168,230,231],{},"Via engine",[146,233,234,237,240,243],{},[168,235,236],{},"Raw write speed on large files",[168,238,239],{},"Good",[168,241,242],{},"Fastest",[168,244,245],{},"Matches the engine",[146,247,248,251,253,256],{},[168,249,250],{},"Preserve styles in source file",[168,252,173],{},[168,254,255],{},"n\u002Fa",[168,257,258],{},"Only via openpyxl",[146,260,261,264,267,269],{},[168,262,263],{},"Quick one-off dump",[168,265,266],{},"Overkill",[168,268,266],{},[168,270,271],{},"Best fit",[39,273,275],{"id":274},"write-1-plain-to_excel-for-a-quick-dump","Write 1: plain to_excel for a quick dump",[10,277,278],{},"No engine argument, no formatting — the fastest way to get a DataFrame onto disk:",[47,280,284],{"className":281,"code":282,"language":283,"meta":52,"style":52},"language-python shiki shiki-themes github-light github-dark","import pandas as pd\n\ndf = pd.DataFrame({\n    \"Region\": [\"North\", \"South\", \"West\"],\n    \"Revenue\": [12000, 9800, 15400],\n})\ndf.to_excel(\"dump.xlsx\", index=False)   # defaults to openpyxl\nprint(\"wrote dump.xlsx\")\n","python",[14,285,286,302,309,321,347,371,377,404],{"__ignoreMap":52},[56,287,288,292,296,299],{"class":58,"line":59},[56,289,291],{"class":290},"szBVR","import",[56,293,295],{"class":294},"sVt8B"," pandas ",[56,297,298],{"class":290},"as",[56,300,301],{"class":294}," pd\n",[56,303,305],{"class":58,"line":304},2,[56,306,308],{"emptyLinePlaceholder":307},true,"\n",[56,310,312,315,318],{"class":58,"line":311},3,[56,313,314],{"class":294},"df ",[56,316,317],{"class":290},"=",[56,319,320],{"class":294}," pd.DataFrame({\n",[56,322,324,327,330,333,336,339,341,344],{"class":58,"line":323},4,[56,325,326],{"class":66},"    \"Region\"",[56,328,329],{"class":294},": [",[56,331,332],{"class":66},"\"North\"",[56,334,335],{"class":294},", ",[56,337,338],{"class":66},"\"South\"",[56,340,335],{"class":294},[56,342,343],{"class":66},"\"West\"",[56,345,346],{"class":294},"],\n",[56,348,350,353,355,359,361,364,366,369],{"class":58,"line":349},5,[56,351,352],{"class":66},"    \"Revenue\"",[56,354,329],{"class":294},[56,356,358],{"class":357},"sj4cs","12000",[56,360,335],{"class":294},[56,362,363],{"class":357},"9800",[56,365,335],{"class":294},[56,367,368],{"class":357},"15400",[56,370,346],{"class":294},[56,372,374],{"class":58,"line":373},6,[56,375,376],{"class":294},"})\n",[56,378,380,383,386,388,392,394,397,400],{"class":58,"line":379},7,[56,381,382],{"class":294},"df.to_excel(",[56,384,385],{"class":66},"\"dump.xlsx\"",[56,387,335],{"class":294},[56,389,391],{"class":390},"s4XuR","index",[56,393,317],{"class":290},[56,395,396],{"class":357},"False",[56,398,399],{"class":294},")   ",[56,401,403],{"class":402},"sJ8bj","# defaults to openpyxl\n",[56,405,407,410,413,416],{"class":58,"line":406},8,[56,408,409],{"class":357},"print",[56,411,412],{"class":294},"(",[56,414,415],{"class":66},"\"wrote dump.xlsx\"",[56,417,418],{"class":294},")\n",[39,420,422],{"id":421},"write-2-xlsxwriter-with-a-cell-format","Write 2: xlsxwriter with a cell format",[10,424,425,426,429],{},"Reach the workbook and worksheet objects through the ",[14,427,428],{},"ExcelWriter",", then add a currency format. xlsxwriter shines here because formatting is first-class:",[47,431,433],{"className":281,"code":432,"language":283,"meta":52,"style":52},"import pandas as pd\n\ndf = pd.DataFrame({\n    \"Region\": [\"North\", \"South\", \"West\"],\n    \"Revenue\": [12000, 9800, 15400],\n})\n\nwith pd.ExcelWriter(\"styled.xlsx\", engine=\"xlsxwriter\") as writer:\n    df.to_excel(writer, sheet_name=\"Report\", index=False)\n    workbook = writer.book\n    worksheet = writer.sheets[\"Report\"]\n    money = workbook.add_format({\"num_format\": \"$#,##0\", \"bold\": True})\n    worksheet.set_column(\"B:B\", 14, money)   # format the Revenue column\n\nprint(\"wrote styled.xlsx\")\n",[14,434,435,445,449,457,475,493,497,501,530,554,565,581,613,633,638],{"__ignoreMap":52},[56,436,437,439,441,443],{"class":58,"line":59},[56,438,291],{"class":290},[56,440,295],{"class":294},[56,442,298],{"class":290},[56,444,301],{"class":294},[56,446,447],{"class":58,"line":304},[56,448,308],{"emptyLinePlaceholder":307},[56,450,451,453,455],{"class":58,"line":311},[56,452,314],{"class":294},[56,454,317],{"class":290},[56,456,320],{"class":294},[56,458,459,461,463,465,467,469,471,473],{"class":58,"line":323},[56,460,326],{"class":66},[56,462,329],{"class":294},[56,464,332],{"class":66},[56,466,335],{"class":294},[56,468,338],{"class":66},[56,470,335],{"class":294},[56,472,343],{"class":66},[56,474,346],{"class":294},[56,476,477,479,481,483,485,487,489,491],{"class":58,"line":349},[56,478,352],{"class":66},[56,480,329],{"class":294},[56,482,358],{"class":357},[56,484,335],{"class":294},[56,486,363],{"class":357},[56,488,335],{"class":294},[56,490,368],{"class":357},[56,492,346],{"class":294},[56,494,495],{"class":58,"line":373},[56,496,376],{"class":294},[56,498,499],{"class":58,"line":379},[56,500,308],{"emptyLinePlaceholder":307},[56,502,503,506,509,512,514,517,519,522,525,527],{"class":58,"line":406},[56,504,505],{"class":290},"with",[56,507,508],{"class":294}," pd.ExcelWriter(",[56,510,511],{"class":66},"\"styled.xlsx\"",[56,513,335],{"class":294},[56,515,516],{"class":390},"engine",[56,518,317],{"class":290},[56,520,521],{"class":66},"\"xlsxwriter\"",[56,523,524],{"class":294},") ",[56,526,298],{"class":290},[56,528,529],{"class":294}," writer:\n",[56,531,533,536,539,541,544,546,548,550,552],{"class":58,"line":532},9,[56,534,535],{"class":294},"    df.to_excel(writer, ",[56,537,538],{"class":390},"sheet_name",[56,540,317],{"class":290},[56,542,543],{"class":66},"\"Report\"",[56,545,335],{"class":294},[56,547,391],{"class":390},[56,549,317],{"class":290},[56,551,396],{"class":357},[56,553,418],{"class":294},[56,555,557,560,562],{"class":58,"line":556},10,[56,558,559],{"class":294},"    workbook ",[56,561,317],{"class":290},[56,563,564],{"class":294}," writer.book\n",[56,566,568,571,573,576,578],{"class":58,"line":567},11,[56,569,570],{"class":294},"    worksheet ",[56,572,317],{"class":290},[56,574,575],{"class":294}," writer.sheets[",[56,577,543],{"class":66},[56,579,580],{"class":294},"]\n",[56,582,584,587,589,592,595,598,601,603,606,608,611],{"class":58,"line":583},12,[56,585,586],{"class":294},"    money ",[56,588,317],{"class":290},[56,590,591],{"class":294}," workbook.add_format({",[56,593,594],{"class":66},"\"num_format\"",[56,596,597],{"class":294},": ",[56,599,600],{"class":66},"\"$#,##0\"",[56,602,335],{"class":294},[56,604,605],{"class":66},"\"bold\"",[56,607,597],{"class":294},[56,609,610],{"class":357},"True",[56,612,376],{"class":294},[56,614,616,619,622,624,627,630],{"class":58,"line":615},13,[56,617,618],{"class":294},"    worksheet.set_column(",[56,620,621],{"class":66},"\"B:B\"",[56,623,335],{"class":294},[56,625,626],{"class":357},"14",[56,628,629],{"class":294},", money)   ",[56,631,632],{"class":402},"# format the Revenue column\n",[56,634,636],{"class":58,"line":635},14,[56,637,308],{"emptyLinePlaceholder":307},[56,639,641,643,645,648],{"class":58,"line":640},15,[56,642,409],{"class":357},[56,644,412],{"class":294},[56,646,647],{"class":66},"\"wrote styled.xlsx\"",[56,649,418],{"class":294},[39,651,653],{"id":652},"write-3-openpyxl-direct-to-edit-an-existing-file","Write 3: openpyxl direct, to edit an existing file",[10,655,656],{},"This is where openpyxl is the only option: load a file that already exists, change a cell, and save. xlsxwriter physically cannot do this because it never reads:",[47,658,660],{"className":281,"code":659,"language":283,"meta":52,"style":52},"import pandas as pd\nfrom openpyxl import load_workbook\n\n# Start from a file written earlier\npd.DataFrame({\"Region\": [\"North\"], \"Revenue\": [12000]}).to_excel(\n    \"ledger.xlsx\", index=False\n)\n\nwb = load_workbook(\"ledger.xlsx\")   # read the existing workbook\nws = wb.active\nws[\"B2\"].value = 13500              # edit a cell in place\nws[\"C1\"] = \"Adjusted\"               # add a column header\nwb.save(\"ledger.xlsx\")\n\nprint(\"updated:\", load_workbook(\"ledger.xlsx\").active[\"B2\"].value)\n",[14,661,662,672,685,689,694,719,733,737,741,759,769,788,806,815,819],{"__ignoreMap":52},[56,663,664,666,668,670],{"class":58,"line":59},[56,665,291],{"class":290},[56,667,295],{"class":294},[56,669,298],{"class":290},[56,671,301],{"class":294},[56,673,674,677,680,682],{"class":58,"line":304},[56,675,676],{"class":290},"from",[56,678,679],{"class":294}," openpyxl ",[56,681,291],{"class":290},[56,683,684],{"class":294}," load_workbook\n",[56,686,687],{"class":58,"line":311},[56,688,308],{"emptyLinePlaceholder":307},[56,690,691],{"class":58,"line":323},[56,692,693],{"class":402},"# Start from a file written earlier\n",[56,695,696,699,702,704,706,709,712,714,716],{"class":58,"line":349},[56,697,698],{"class":294},"pd.DataFrame({",[56,700,701],{"class":66},"\"Region\"",[56,703,329],{"class":294},[56,705,332],{"class":66},[56,707,708],{"class":294},"], ",[56,710,711],{"class":66},"\"Revenue\"",[56,713,329],{"class":294},[56,715,358],{"class":357},[56,717,718],{"class":294},"]}).to_excel(\n",[56,720,721,724,726,728,730],{"class":58,"line":373},[56,722,723],{"class":66},"    \"ledger.xlsx\"",[56,725,335],{"class":294},[56,727,391],{"class":390},[56,729,317],{"class":290},[56,731,732],{"class":357},"False\n",[56,734,735],{"class":58,"line":379},[56,736,418],{"class":294},[56,738,739],{"class":58,"line":406},[56,740,308],{"emptyLinePlaceholder":307},[56,742,743,746,748,751,754,756],{"class":58,"line":532},[56,744,745],{"class":294},"wb ",[56,747,317],{"class":290},[56,749,750],{"class":294}," load_workbook(",[56,752,753],{"class":66},"\"ledger.xlsx\"",[56,755,399],{"class":294},[56,757,758],{"class":402},"# read the existing workbook\n",[56,760,761,764,766],{"class":58,"line":556},[56,762,763],{"class":294},"ws ",[56,765,317],{"class":290},[56,767,768],{"class":294}," wb.active\n",[56,770,771,774,777,780,782,785],{"class":58,"line":567},[56,772,773],{"class":294},"ws[",[56,775,776],{"class":66},"\"B2\"",[56,778,779],{"class":294},"].value ",[56,781,317],{"class":290},[56,783,784],{"class":357}," 13500",[56,786,787],{"class":402},"              # edit a cell in place\n",[56,789,790,792,795,798,800,803],{"class":58,"line":583},[56,791,773],{"class":294},[56,793,794],{"class":66},"\"C1\"",[56,796,797],{"class":294},"] ",[56,799,317],{"class":290},[56,801,802],{"class":66}," \"Adjusted\"",[56,804,805],{"class":402},"               # add a column header\n",[56,807,808,811,813],{"class":58,"line":615},[56,809,810],{"class":294},"wb.save(",[56,812,753],{"class":66},[56,814,418],{"class":294},[56,816,817],{"class":58,"line":635},[56,818,308],{"emptyLinePlaceholder":307},[56,820,821,823,825,828,831,833,836,838],{"class":58,"line":640},[56,822,409],{"class":357},[56,824,412],{"class":294},[56,826,827],{"class":66},"\"updated:\"",[56,829,830],{"class":294},", load_workbook(",[56,832,753],{"class":66},[56,834,835],{"class":294},").active[",[56,837,776],{"class":66},[56,839,840],{"class":294},"].value)\n",[39,842,844],{"id":843},"appending-a-sheet-needs-openpyxl","Appending a sheet needs openpyxl",[10,846,847,849,850,853,854,856,857,100],{},[14,848,125],{}," opens the existing file to add to it, so it requires the read-capable engine. Passing ",[14,851,852],{},"engine=\"xlsxwriter\""," with ",[14,855,125],{}," raises a ",[14,858,859],{},"ValueError",[47,861,863],{"className":281,"code":862,"language":283,"meta":52,"style":52},"import pandas as pd\n\npd.DataFrame({\"Region\": [\"North\"], \"Revenue\": [12000]}).to_excel(\n    \"book.xlsx\", sheet_name=\"Jan\", index=False\n)\n\nwith pd.ExcelWriter(\"book.xlsx\", engine=\"openpyxl\", mode=\"a\") as writer:\n    pd.DataFrame({\"Region\": [\"South\"], \"Revenue\": [9800]}).to_excel(\n        writer, sheet_name=\"Feb\", index=False\n    )\n\nprint(pd.ExcelFile(\"book.xlsx\").sheet_names)\n",[14,864,865,875,879,899,921,925,929,963,984,1004,1009,1013],{"__ignoreMap":52},[56,866,867,869,871,873],{"class":58,"line":59},[56,868,291],{"class":290},[56,870,295],{"class":294},[56,872,298],{"class":290},[56,874,301],{"class":294},[56,876,877],{"class":58,"line":304},[56,878,308],{"emptyLinePlaceholder":307},[56,880,881,883,885,887,889,891,893,895,897],{"class":58,"line":311},[56,882,698],{"class":294},[56,884,701],{"class":66},[56,886,329],{"class":294},[56,888,332],{"class":66},[56,890,708],{"class":294},[56,892,711],{"class":66},[56,894,329],{"class":294},[56,896,358],{"class":357},[56,898,718],{"class":294},[56,900,901,904,906,908,910,913,915,917,919],{"class":58,"line":323},[56,902,903],{"class":66},"    \"book.xlsx\"",[56,905,335],{"class":294},[56,907,538],{"class":390},[56,909,317],{"class":290},[56,911,912],{"class":66},"\"Jan\"",[56,914,335],{"class":294},[56,916,391],{"class":390},[56,918,317],{"class":290},[56,920,732],{"class":357},[56,922,923],{"class":58,"line":349},[56,924,418],{"class":294},[56,926,927],{"class":58,"line":373},[56,928,308],{"emptyLinePlaceholder":307},[56,930,931,933,935,938,940,942,944,947,949,952,954,957,959,961],{"class":58,"line":379},[56,932,505],{"class":290},[56,934,508],{"class":294},[56,936,937],{"class":66},"\"book.xlsx\"",[56,939,335],{"class":294},[56,941,516],{"class":390},[56,943,317],{"class":290},[56,945,946],{"class":66},"\"openpyxl\"",[56,948,335],{"class":294},[56,950,951],{"class":390},"mode",[56,953,317],{"class":290},[56,955,956],{"class":66},"\"a\"",[56,958,524],{"class":294},[56,960,298],{"class":290},[56,962,529],{"class":294},[56,964,965,968,970,972,974,976,978,980,982],{"class":58,"line":406},[56,966,967],{"class":294},"    pd.DataFrame({",[56,969,701],{"class":66},[56,971,329],{"class":294},[56,973,338],{"class":66},[56,975,708],{"class":294},[56,977,711],{"class":66},[56,979,329],{"class":294},[56,981,363],{"class":357},[56,983,718],{"class":294},[56,985,986,989,991,993,996,998,1000,1002],{"class":58,"line":532},[56,987,988],{"class":294},"        writer, ",[56,990,538],{"class":390},[56,992,317],{"class":290},[56,994,995],{"class":66},"\"Feb\"",[56,997,335],{"class":294},[56,999,391],{"class":390},[56,1001,317],{"class":290},[56,1003,732],{"class":357},[56,1005,1006],{"class":58,"line":556},[56,1007,1008],{"class":294},"    )\n",[56,1010,1011],{"class":58,"line":567},[56,1012,308],{"emptyLinePlaceholder":307},[56,1014,1015,1017,1020,1022],{"class":58,"line":583},[56,1016,409],{"class":357},[56,1018,1019],{"class":294},"(pd.ExcelFile(",[56,1021,937],{"class":66},[56,1023,1024],{"class":294},").sheet_names)\n",[39,1026,1028],{"id":1027},"common-pitfalls","Common pitfalls",[140,1030,1031,1044],{},[143,1032,1033],{},[146,1034,1035,1038,1041],{},[149,1036,1037],{},"Symptom",[149,1039,1040],{},"Cause",[149,1042,1043],{},"Fix",[163,1045,1046,1064,1081,1098],{},[146,1047,1048,1053,1056],{},[168,1049,1050],{},[14,1051,1052],{},"ValueError: Append mode is not supported with xlsxwriter",[168,1054,1055],{},"xlsxwriter can't open existing files",[168,1057,1058,1059,92,1062],{},"Use ",[14,1060,1061],{},"engine=\"openpyxl\"",[14,1063,125],{},[146,1065,1066,1072,1075],{},[168,1067,1068,1071],{},[14,1069,1070],{},"FileNotFoundError","\u002Fempty file when \"editing\"",[168,1073,1074],{},"Tried to load an existing file with xlsxwriter",[168,1076,1058,1077,1080],{},[14,1078,1079],{},"load_workbook"," (openpyxl); xlsxwriter only creates new files",[146,1082,1083,1086,1089],{},[168,1084,1085],{},"Styles vanish after a pandas round-trip",[168,1087,1088],{},"Reading to a DataFrame keeps values, not formatting",[168,1090,1091,1092,1095,1096],{},"Edit with openpyxl directly instead of ",[14,1093,1094],{},"read_excel"," then ",[14,1097,161],{},[146,1099,1100,1103,1106],{},[168,1101,1102],{},"Chart or conditional format ignored",[168,1104,1105],{},"Tried to add it through the wrong engine",[168,1107,1108],{},"Build new styled reports with xlsxwriter",[10,1110,1111,1112,1115,1116,1119],{},"The styling-loss trap is the one that bites teams: ",[14,1113,1114],{},"pd.read_excel()"," returns only the data, so a ",[14,1117,1118],{},"read_excel → to_excel"," cycle silently drops every fill, border, and number format the source had. To keep formatting, never pass through a DataFrame — open the file with openpyxl and modify cells in place.",[39,1121,1123],{"id":1122},"performance-and-scale","Performance and scale",[10,1125,1126,1127,1130,1131,1134,1135,1137],{},"For large write-only jobs, xlsxwriter is the fastest and supports ",[14,1128,1129],{},"constant_memory"," mode to stream rows without holding the whole sheet in RAM. openpyxl is competitive and adds a ",[14,1132,1133],{},"write_only=True"," mode for the same reason, but it pays a cost to preserve existing content. For a quick dump under a few thousand rows, the engine choice is irrelevant — use plain ",[14,1136,161],{},".",[39,1139,1141],{"id":1140},"frequently-asked-questions","Frequently asked questions",[10,1143,1144,1147,1148,1137],{},[19,1145,1146],{},"Is pandas.ExcelWriter an engine?","\nNo. It's a context-manager wrapper that delegates to openpyxl or xlsxwriter. You pick the real engine with ",[14,1149,99],{},[10,1151,1152,1158,1159,1161,1162,1164],{},[19,1153,1154,1155,1157],{},"Which engine does ",[14,1156,161],{}," use by default?","\nFor ",[14,1160,95],{},", pandas defaults to openpyxl when it's installed. Name ",[14,1163,852],{}," explicitly when you want xlsxwriter's formatting.",[10,1166,1167,1170],{},[19,1168,1169],{},"Can xlsxwriter edit an existing workbook?","\nNever. It is write-only and creates a fresh file each run. Any in-place edit or append must go through openpyxl.",[10,1172,1173,1176,1177,1179,1180,22,1183,1186],{},[19,1174,1175],{},"How do I add a chart to a pandas export?","\nWrite with ",[14,1178,852],{},", grab ",[14,1181,1182],{},"writer.book",[14,1184,1185],{},"writer.sheets[...]",", then build the chart with xlsxwriter's chart API.",[39,1188,1190],{"id":1189},"conclusion","Conclusion",[10,1192,1193,1194,1196,1197,1199],{},"Think in layers: ",[14,1195,16],{}," is the wrapper, openpyxl and xlsxwriter are the engines. Reach for openpyxl when you must read, edit in place, or append to a file that already exists; reach for xlsxwriter to build new, heavily styled reports and charts from scratch; and use plain ",[14,1198,161],{}," for a quick dump where formatting doesn't matter. The single rule that prevents most surprises: a pandas round-trip keeps data but loses styling, so edit existing files with openpyxl directly.",[39,1201,1203],{"id":1202},"where-to-go-next","Where to go next",[10,1205,1206,1207,1209,1210,1214,1215,1219,1220,1137],{},"Return to ",[33,1208,36],{"href":35}," for the full export workflow. To drop the index column cleanly on any of these engines, see ",[33,1211,1213],{"href":1212},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-pandas-dataframe-to-excel-without-index\u002F","Write a Pandas DataFrame to Excel Without the Index",". For the openpyxl append pattern in depth, read ",[33,1216,1218],{"href":1217},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fopenpyxl-append-data-to-existing-excel-sheet\u002F","Append Data to an Existing Excel Sheet with openpyxl",". To take styling further, explore ",[33,1221,1223],{"href":1222},"\u002Fformatting-and-charting-excel-reports-with-python\u002F","Formatting and Charting Excel Reports with Python",[1225,1226,1227],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":52,"searchDepth":304,"depth":304,"links":1229},[1230,1231,1232,1233,1234,1235,1236,1237,1238,1239,1240,1241],{"id":41,"depth":304,"text":42},{"id":79,"depth":304,"text":80},{"id":137,"depth":304,"text":138},{"id":274,"depth":304,"text":275},{"id":421,"depth":304,"text":422},{"id":652,"depth":304,"text":653},{"id":843,"depth":304,"text":844},{"id":1027,"depth":304,"text":1028},{"id":1122,"depth":304,"text":1123},{"id":1140,"depth":304,"text":1141},{"id":1189,"depth":304,"text":1190},{"id":1202,"depth":304,"text":1203},"Understand how pandas.ExcelWriter wraps the openpyxl and xlsxwriter engines, when each one wins, and write the same DataFrame three ways with a clear decision table.","md",{},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter",{"title":5,"description":1247},"pandas.ExcelWriter wraps an engine — openpyxl reads, edits and appends; xlsxwriter is write-only but fast with rich formatting. A decision table and three runnable writes.","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter\u002Findex","meoidTzCRSLcpQ1octekPOUvqqLyfKmAmdTRDaXtAT0",[1251,1254],{"title":36,"path":1252,"stem":1253,"children":-1},"\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",{"title":1213,"path":1255,"stem":1256,"children":-1},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-pandas-dataframe-to-excel-without-index","getting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fwrite-pandas-dataframe-to-excel-without-index\u002Findex",1781773160959]