[{"data":1,"prerenderedAt":1877},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation":1868},{"id":4,"title":5,"body":6,"dateModified":1840,"datePublished":1840,"description":1841,"extension":1842,"faq":1843,"meta":1852,"navigation":219,"path":1861,"seo":1862,"slug":1864,"stem":1865,"type":1866,"__hash__":1867},"docs\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation\u002Findex.md","openpyxl vs pandas for Excel Automation",{"type":7,"value":8,"toc":1826},"minimark",[9,19,148,153,181,184,188,191,412,415,596,603,607,610,689,796,804,808,815,1166,1172,1176,1190,1282,1289,1293,1296,1305,1311,1317,1328,1339,1342,1346,1349,1379,1382,1386,1490,1494,1579,1590,1741,1745,1751,1755,1762,1768,1774,1780,1784,1822],[10,11,12,13,18],"p",{},"openpyxl and pandas are the two libraries almost every Python Excel project installs, and the\nquestion of which to use is confused by the fact that pandas uses openpyxl underneath. They are not\nalternatives at the same level: pandas is a table library that happens to read and write\nspreadsheets, and openpyxl is a spreadsheet library that knows nothing about tables. This guide,\npart of ",[14,15,17],"a",{"href":16},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002F","Choosing a Python Excel Library",",\nsets out the line between them and shows the handoff that gets both jobs done in one script.",[20,21,29,30,29,34,29,38,29,45,29,55,29,62,29,69,29,74,29,78,29,82,29,86,29,91,29,96,29,100,29,103,29,106,29,109,29,112,29,120,29,126,29,131,29,136,29,140,29,143],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"0 0 760 224","img",[25,26],"opd-line-t","opd-line-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:760px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"Where the line falls between pandas and openpyxl",[35,36,37],"desc",{"id":26},"pandas owns the values — filtering, grouping and joining rows. openpyxl owns the document — fonts, number formats, widths, merges and comments. A handoff between them finishes both halves.",[39,40],"rect",{"x":41,"y":41,"width":42,"height":43,"fill":44},"0","760","224","#ffffff",[39,46],{"x":47,"y":48,"width":49,"height":50,"rx":51,"fill":52,"stroke":53,"style":54},"20","28","270.0","162","14","#d9f4f1","var(--teal,#0f9488)","stroke-width:2px",[56,57,61],"text",{"x":58,"y":59,"style":60},"155.0","54","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","pandas: the data",[63,64],"line",{"x1":65,"y1":66,"x2":67,"y2":66,"stroke":53,"style":68},"36","64","274.0","stroke-width:1px",[56,70,73],{"x":58,"y":71,"style":72},"86","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","filter and group rows",[56,75,77],{"x":58,"y":76,"style":72},"109","join tables",[56,79,81],{"x":58,"y":80,"style":72},"132","typed columns",[56,83,85],{"x":58,"y":84,"style":72},"155","no idea how it looks",[39,87],{"x":88,"y":48,"width":49,"height":50,"rx":51,"fill":89,"stroke":90,"style":54},"470.0","#f0f4ff","var(--brand,#5b5cf0)",[56,92,95],{"x":93,"y":59,"style":94},"605.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","openpyxl: the file",[63,97],{"x1":98,"y1":66,"x2":99,"y2":66,"stroke":90,"style":68},"486.0","724.0",[56,101,102],{"x":93,"y":71,"style":72},"fonts and fills",[56,104,105],{"x":93,"y":76,"style":72},"number formats",[56,107,108],{"x":93,"y":80,"style":72},"widths and freezes",[56,110,111],{"x":93,"y":84,"style":72},"no idea what it means",[39,113],{"x":114,"y":115,"width":116,"height":117,"rx":118,"fill":119,"stroke":90},"316.0","90.0","128","38","19","#ebebfd",[56,121,125],{"x":122,"y":123,"style":124},"380.0","114.0","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","handoff",[63,127],{"x1":128,"y1":129,"x2":130,"y2":129,"stroke":90,"style":54},"295.0","109.0","309.0",[132,133],"polygon",{"points":134,"fill":135},"309.0,109.0 300.0,104.0 300.0,114.0","#5b5cf0",[63,137],{"x1":138,"y1":129,"x2":139,"y2":129,"stroke":90,"style":54},"449.0","463.0",[132,141],{"points":142,"fill":135},"463.0,109.0 454.0,104.0 454.0,114.0",[56,144,147],{"x":122,"y":145,"style":146},"210","font-size:12.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","the split is rows versus cells, not old versus new",[149,150,152],"h2",{"id":151},"prerequisites","Prerequisites",[154,155,160],"pre",{"className":156,"code":157,"language":158,"meta":159,"style":159},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[161,162,163],"code",{"__ignoreMap":159},[164,165,167,171,175,178],"span",{"class":63,"line":166},1,[164,168,170],{"class":169},"sMTad","pip",[164,172,174],{"class":173},"srMev"," install",[164,176,177],{"class":173}," pandas",[164,179,180],{"class":173}," openpyxl\n",[10,182,183],{},"Every snippet below writes its own sample workbook first, so you can paste and run them in order.",[149,185,187],{"id":186},"the-dividing-line-rows-versus-cells","The dividing line: rows versus cells",[10,189,190],{},"pandas thinks in columns and rows of values. openpyxl thinks in cells, each carrying a value, a\nnumber format, a font, a fill, a border and a comment. If what you need can be expressed as \"filter\nthese rows, group by that column, total this one\" — pandas. If it can only be expressed as \"make\nrow 1 bold, freeze it, and set column B to two decimal places\" — openpyxl.",[154,192,196],{"className":193,"code":194,"language":195,"meta":159,"style":159},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nsales = pd.DataFrame({\n    \"Region\": [\"North\", \"South\", \"North\", \"West\"],\n    \"Rep\": [\"Ana\", \"Ben\", \"Cara\", \"Dev\"],\n    \"Revenue\": [12400.0, 9800.5, 15320.25, 7010.0],\n})\nsales.to_excel(\"sales.xlsx\", sheet_name=\"Raw\", index=False)\n\n# pandas: the shape of the data\nsummary = sales.groupby(\"Region\", as_index=False)[\"Revenue\"].sum()\nprint(summary)\n","python",[161,197,198,214,221,233,263,291,320,326,359,364,371,403],{"__ignoreMap":159},[164,199,200,204,208,211],{"class":63,"line":166},[164,201,203],{"class":202},"s-kum","import",[164,205,207],{"class":206},"skGVy"," pandas ",[164,209,210],{"class":202},"as",[164,212,213],{"class":206}," pd\n",[164,215,217],{"class":63,"line":216},2,[164,218,220],{"emptyLinePlaceholder":219},true,"\n",[164,222,224,227,230],{"class":63,"line":223},3,[164,225,226],{"class":206},"sales ",[164,228,229],{"class":202},"=",[164,231,232],{"class":206}," pd.DataFrame({\n",[164,234,236,239,242,245,248,251,253,255,257,260],{"class":63,"line":235},4,[164,237,238],{"class":173},"    \"Region\"",[164,240,241],{"class":206},": [",[164,243,244],{"class":173},"\"North\"",[164,246,247],{"class":206},", ",[164,249,250],{"class":173},"\"South\"",[164,252,247],{"class":206},[164,254,244],{"class":173},[164,256,247],{"class":206},[164,258,259],{"class":173},"\"West\"",[164,261,262],{"class":206},"],\n",[164,264,266,269,271,274,276,279,281,284,286,289],{"class":63,"line":265},5,[164,267,268],{"class":173},"    \"Rep\"",[164,270,241],{"class":206},[164,272,273],{"class":173},"\"Ana\"",[164,275,247],{"class":206},[164,277,278],{"class":173},"\"Ben\"",[164,280,247],{"class":206},[164,282,283],{"class":173},"\"Cara\"",[164,285,247],{"class":206},[164,287,288],{"class":173},"\"Dev\"",[164,290,262],{"class":206},[164,292,294,297,299,303,305,308,310,313,315,318],{"class":63,"line":293},6,[164,295,296],{"class":173},"    \"Revenue\"",[164,298,241],{"class":206},[164,300,302],{"class":301},"sP0c6","12400.0",[164,304,247],{"class":206},[164,306,307],{"class":301},"9800.5",[164,309,247],{"class":206},[164,311,312],{"class":301},"15320.25",[164,314,247],{"class":206},[164,316,317],{"class":301},"7010.0",[164,319,262],{"class":206},[164,321,323],{"class":63,"line":322},7,[164,324,325],{"class":206},"})\n",[164,327,329,332,335,337,341,343,346,348,351,353,356],{"class":63,"line":328},8,[164,330,331],{"class":206},"sales.to_excel(",[164,333,334],{"class":173},"\"sales.xlsx\"",[164,336,247],{"class":206},[164,338,340],{"class":339},"sa561","sheet_name",[164,342,229],{"class":202},[164,344,345],{"class":173},"\"Raw\"",[164,347,247],{"class":206},[164,349,350],{"class":339},"index",[164,352,229],{"class":202},[164,354,355],{"class":301},"False",[164,357,358],{"class":206},")\n",[164,360,362],{"class":63,"line":361},9,[164,363,220],{"emptyLinePlaceholder":219},[164,365,367],{"class":63,"line":366},10,[164,368,370],{"class":369},"s-wDw","# pandas: the shape of the data\n",[164,372,374,377,379,382,385,387,390,392,394,397,400],{"class":63,"line":373},11,[164,375,376],{"class":206},"summary ",[164,378,229],{"class":202},[164,380,381],{"class":206}," sales.groupby(",[164,383,384],{"class":173},"\"Region\"",[164,386,247],{"class":206},[164,388,389],{"class":339},"as_index",[164,391,229],{"class":202},[164,393,355],{"class":301},[164,395,396],{"class":206},")[",[164,398,399],{"class":173},"\"Revenue\"",[164,401,402],{"class":206},"].sum()\n",[164,404,406,409],{"class":63,"line":405},12,[164,407,408],{"class":301},"print",[164,410,411],{"class":206},"(summary)\n",[10,413,414],{},"That aggregation in openpyxl would mean iterating rows, accumulating into a dictionary and sorting\nthe result by hand — perhaps twenty lines to pandas' one. The reverse is just as lopsided.",[154,416,418],{"className":193,"code":417,"language":195,"meta":159,"style":159},"from openpyxl import load_workbook\nfrom openpyxl.styles import Font\n\nbook = load_workbook(\"sales.xlsx\")\nsheet = book[\"Raw\"]\n\n# openpyxl: the appearance of the file\nfor cell in sheet[1]:\n    cell.font = Font(bold=True)\nsheet.freeze_panes = \"A2\"\nfor cell in sheet[\"C\"][1:]:\n    cell.number_format = \"#,##0.00\"\nsheet.column_dimensions[\"A\"].width = 14\nbook.save(\"sales-styled.xlsx\")\n",[161,419,420,433,445,449,463,478,482,487,507,527,537,558,568,585],{"__ignoreMap":159},[164,421,422,425,428,430],{"class":63,"line":166},[164,423,424],{"class":202},"from",[164,426,427],{"class":206}," openpyxl ",[164,429,203],{"class":202},[164,431,432],{"class":206}," load_workbook\n",[164,434,435,437,440,442],{"class":63,"line":216},[164,436,424],{"class":202},[164,438,439],{"class":206}," openpyxl.styles ",[164,441,203],{"class":202},[164,443,444],{"class":206}," Font\n",[164,446,447],{"class":63,"line":223},[164,448,220],{"emptyLinePlaceholder":219},[164,450,451,454,456,459,461],{"class":63,"line":235},[164,452,453],{"class":206},"book ",[164,455,229],{"class":202},[164,457,458],{"class":206}," load_workbook(",[164,460,334],{"class":173},[164,462,358],{"class":206},[164,464,465,468,470,473,475],{"class":63,"line":265},[164,466,467],{"class":206},"sheet ",[164,469,229],{"class":202},[164,471,472],{"class":206}," book[",[164,474,345],{"class":173},[164,476,477],{"class":206},"]\n",[164,479,480],{"class":63,"line":293},[164,481,220],{"emptyLinePlaceholder":219},[164,483,484],{"class":63,"line":322},[164,485,486],{"class":369},"# openpyxl: the appearance of the file\n",[164,488,489,492,495,498,501,504],{"class":63,"line":328},[164,490,491],{"class":202},"for",[164,493,494],{"class":206}," cell ",[164,496,497],{"class":202},"in",[164,499,500],{"class":206}," sheet[",[164,502,503],{"class":301},"1",[164,505,506],{"class":206},"]:\n",[164,508,509,512,514,517,520,522,525],{"class":63,"line":361},[164,510,511],{"class":206},"    cell.font ",[164,513,229],{"class":202},[164,515,516],{"class":206}," Font(",[164,518,519],{"class":339},"bold",[164,521,229],{"class":202},[164,523,524],{"class":301},"True",[164,526,358],{"class":206},[164,528,529,532,534],{"class":63,"line":366},[164,530,531],{"class":206},"sheet.freeze_panes ",[164,533,229],{"class":202},[164,535,536],{"class":173}," \"A2\"\n",[164,538,539,541,543,545,547,550,553,555],{"class":63,"line":373},[164,540,491],{"class":202},[164,542,494],{"class":206},[164,544,497],{"class":202},[164,546,500],{"class":206},[164,548,549],{"class":173},"\"C\"",[164,551,552],{"class":206},"][",[164,554,503],{"class":301},[164,556,557],{"class":206},":]:\n",[164,559,560,563,565],{"class":63,"line":405},[164,561,562],{"class":206},"    cell.number_format ",[164,564,229],{"class":202},[164,566,567],{"class":173}," \"#,##0.00\"\n",[164,569,571,574,577,580,582],{"class":63,"line":570},13,[164,572,573],{"class":206},"sheet.column_dimensions[",[164,575,576],{"class":173},"\"A\"",[164,578,579],{"class":206},"].width ",[164,581,229],{"class":202},[164,583,584],{"class":301}," 14\n",[164,586,588,591,594],{"class":63,"line":587},14,[164,589,590],{"class":206},"book.save(",[164,592,593],{"class":173},"\"sales-styled.xlsx\"",[164,595,358],{"class":206},[10,597,598,599,602],{},"pandas has no vocabulary for any of that. ",[161,600,601],{},"to_excel"," writes values and nothing else.",[149,604,606],{"id":605},"reading-the-same-parser-two-different-results","Reading: the same parser, two different results",[10,608,609],{},"Both calls below read the same file with the same code underneath. The difference is what you get\nback: a typed table, or a grid of cells you can inspect individually.",[20,611,29,616,29,619,29,622,29,625,29,630,29,639,29,645,29,650,29,655,29,658,29,661,29,665,29,668,29,672,29,675,29,678,29,683,29,686],{"viewBox":612,"role":23,"ariaLabelledBy":613,"xmlns":27,"style":28},"0 0 760 232",[614,615],"opd-read-t","opd-read-d",[31,617,618],{"id":614},"The same parser, two different return values",[35,620,621],{"id":615},"A workbook is parsed by openpyxl in both cases. Through pandas it becomes a typed DataFrame; used directly it stays a grid of cells carrying formats and comments.",[39,623],{"x":41,"y":41,"width":42,"height":624,"fill":44},"232",[56,626,629],{"x":122,"y":627,"style":628},"32","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","read paths",[39,631],{"x":632,"y":633,"width":634,"height":635,"rx":636,"fill":637,"stroke":638,"style":54},"24.0","74","208.0","96","12","#e7ebef","var(--line,#cdd5e6)",[56,640,644],{"x":641,"y":642,"style":643},"128.0","114","font-size:14px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","sales.xlsx",[56,646,649],{"x":641,"y":647,"style":648},"136","font-size:11.5px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","one file on disk",[63,651],{"x1":652,"y1":653,"x2":654,"y2":653,"stroke":90,"style":54},"237.0","122.0","269.0",[132,656],{"points":657,"fill":135},"269.0,122.0 260.0,117.0 260.0,127.0",[39,659],{"x":660,"y":633,"width":634,"height":635,"rx":636,"fill":89,"stroke":90,"style":54},"276.0",[56,662,664],{"x":122,"y":642,"style":663},"font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","openpyxl parser",[56,666,667],{"x":122,"y":647,"style":648},"reads the sheet XML",[63,669],{"x1":670,"y1":653,"x2":671,"y2":653,"stroke":90,"style":54},"489.0","521.0",[132,673],{"points":674,"fill":135},"521.0,122.0 512.0,117.0 512.0,127.0",[39,676],{"x":677,"y":633,"width":634,"height":635,"rx":636,"fill":52,"stroke":53,"style":54},"528.0",[56,679,682],{"x":680,"y":642,"style":681},"632.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","DataFrame or cells",[56,684,685],{"x":680,"y":647,"style":648},"typed table, or formats",[56,687,688],{"x":122,"y":145,"style":146},"pandas cannot be faster than the parser it delegates to",[154,690,692],{"className":193,"code":691,"language":195,"meta":159,"style":159},"import pandas as pd\nfrom openpyxl import load_workbook\n\nframe = pd.read_excel(\"sales.xlsx\", sheet_name=\"Raw\")\nprint(frame.dtypes)                      # typed columns, ready to aggregate\n\nbook = load_workbook(\"sales.xlsx\")\nsheet = book[\"Raw\"]\nprint(sheet[\"C2\"].value, sheet[\"C2\"].number_format, sheet.max_row)\n",[161,693,694,704,714,718,740,750,754,766,778],{"__ignoreMap":159},[164,695,696,698,700,702],{"class":63,"line":166},[164,697,203],{"class":202},[164,699,207],{"class":206},[164,701,210],{"class":202},[164,703,213],{"class":206},[164,705,706,708,710,712],{"class":63,"line":216},[164,707,424],{"class":202},[164,709,427],{"class":206},[164,711,203],{"class":202},[164,713,432],{"class":206},[164,715,716],{"class":63,"line":223},[164,717,220],{"emptyLinePlaceholder":219},[164,719,720,723,725,728,730,732,734,736,738],{"class":63,"line":235},[164,721,722],{"class":206},"frame ",[164,724,229],{"class":202},[164,726,727],{"class":206}," pd.read_excel(",[164,729,334],{"class":173},[164,731,247],{"class":206},[164,733,340],{"class":339},[164,735,229],{"class":202},[164,737,345],{"class":173},[164,739,358],{"class":206},[164,741,742,744,747],{"class":63,"line":265},[164,743,408],{"class":301},[164,745,746],{"class":206},"(frame.dtypes)                      ",[164,748,749],{"class":369},"# typed columns, ready to aggregate\n",[164,751,752],{"class":63,"line":293},[164,753,220],{"emptyLinePlaceholder":219},[164,755,756,758,760,762,764],{"class":63,"line":322},[164,757,453],{"class":206},[164,759,229],{"class":202},[164,761,458],{"class":206},[164,763,334],{"class":173},[164,765,358],{"class":206},[164,767,768,770,772,774,776],{"class":63,"line":328},[164,769,467],{"class":206},[164,771,229],{"class":202},[164,773,472],{"class":206},[164,775,345],{"class":173},[164,777,477],{"class":206},[164,779,780,782,785,788,791,793],{"class":63,"line":361},[164,781,408],{"class":301},[164,783,784],{"class":206},"(sheet[",[164,786,787],{"class":173},"\"C2\"",[164,789,790],{"class":206},"].value, sheet[",[164,792,787],{"class":173},[164,794,795],{"class":206},"].number_format, sheet.max_row)\n",[10,797,798,799,803],{},"pandas gives you dtypes, alignment and vectorised operations. openpyxl gives you the number format,\nthe merged-cell ranges, the comment on C2 and the fact that the sheet has 5 rows — none of which\nsurvive the trip into a DataFrame. When a read has to answer a question ",[800,801,802],"em",{},"about the file"," rather\nthan about the data, openpyxl is the only one of the two that can.",[149,805,807],{"id":806},"writing-values-versus-a-finished-document","Writing: values versus a finished document",[10,809,810,811,814],{},"The clean pattern is to let each library do its half in sequence. pandas produces the values;\nopenpyxl — reached through ",[161,812,813],{},"pandas.ExcelWriter"," so nothing has to be reopened — finishes the sheet.",[154,816,818],{"className":193,"code":817,"language":195,"meta":159,"style":159},"import pandas as pd\nfrom openpyxl.styles import Alignment, Font\nfrom openpyxl.utils import get_column_letter\n\nsummary = sales.groupby(\"Region\", as_index=False)[\"Revenue\"].sum()\n\nwith pd.ExcelWriter(\"regional.xlsx\", engine=\"openpyxl\") as writer:\n    summary.to_excel(writer, sheet_name=\"By region\", index=False)\n    sheet = writer.sheets[\"By region\"]\n    for cell in sheet[1]:\n        cell.font = Font(bold=True, color=\"FFFFFF\")\n        cell.alignment = Alignment(horizontal=\"center\")\n    for row in sheet.iter_rows(min_row=2, min_col=2, max_col=2):\n        for cell in row:\n            cell.number_format = \"#,##0.00\"\n    for index, column in enumerate(summary.columns, start=1):\n        width = max(len(str(column)), *(len(str(v)) for v in summary[column])) + 4\n        sheet.column_dimensions[get_column_letter(index)].width = width\n",[161,819,820,830,841,853,857,881,885,914,936,950,965,992,1012,1053,1065,1075,1100,1155],{"__ignoreMap":159},[164,821,822,824,826,828],{"class":63,"line":166},[164,823,203],{"class":202},[164,825,207],{"class":206},[164,827,210],{"class":202},[164,829,213],{"class":206},[164,831,832,834,836,838],{"class":63,"line":216},[164,833,424],{"class":202},[164,835,439],{"class":206},[164,837,203],{"class":202},[164,839,840],{"class":206}," Alignment, Font\n",[164,842,843,845,848,850],{"class":63,"line":223},[164,844,424],{"class":202},[164,846,847],{"class":206}," openpyxl.utils ",[164,849,203],{"class":202},[164,851,852],{"class":206}," get_column_letter\n",[164,854,855],{"class":63,"line":235},[164,856,220],{"emptyLinePlaceholder":219},[164,858,859,861,863,865,867,869,871,873,875,877,879],{"class":63,"line":265},[164,860,376],{"class":206},[164,862,229],{"class":202},[164,864,381],{"class":206},[164,866,384],{"class":173},[164,868,247],{"class":206},[164,870,389],{"class":339},[164,872,229],{"class":202},[164,874,355],{"class":301},[164,876,396],{"class":206},[164,878,399],{"class":173},[164,880,402],{"class":206},[164,882,883],{"class":63,"line":293},[164,884,220],{"emptyLinePlaceholder":219},[164,886,887,890,893,896,898,901,903,906,909,911],{"class":63,"line":322},[164,888,889],{"class":202},"with",[164,891,892],{"class":206}," pd.ExcelWriter(",[164,894,895],{"class":173},"\"regional.xlsx\"",[164,897,247],{"class":206},[164,899,900],{"class":339},"engine",[164,902,229],{"class":202},[164,904,905],{"class":173},"\"openpyxl\"",[164,907,908],{"class":206},") ",[164,910,210],{"class":202},[164,912,913],{"class":206}," writer:\n",[164,915,916,919,921,923,926,928,930,932,934],{"class":63,"line":328},[164,917,918],{"class":206},"    summary.to_excel(writer, ",[164,920,340],{"class":339},[164,922,229],{"class":202},[164,924,925],{"class":173},"\"By region\"",[164,927,247],{"class":206},[164,929,350],{"class":339},[164,931,229],{"class":202},[164,933,355],{"class":301},[164,935,358],{"class":206},[164,937,938,941,943,946,948],{"class":63,"line":361},[164,939,940],{"class":206},"    sheet ",[164,942,229],{"class":202},[164,944,945],{"class":206}," writer.sheets[",[164,947,925],{"class":173},[164,949,477],{"class":206},[164,951,952,955,957,959,961,963],{"class":63,"line":366},[164,953,954],{"class":202},"    for",[164,956,494],{"class":206},[164,958,497],{"class":202},[164,960,500],{"class":206},[164,962,503],{"class":301},[164,964,506],{"class":206},[164,966,967,970,972,974,976,978,980,982,985,987,990],{"class":63,"line":373},[164,968,969],{"class":206},"        cell.font ",[164,971,229],{"class":202},[164,973,516],{"class":206},[164,975,519],{"class":339},[164,977,229],{"class":202},[164,979,524],{"class":301},[164,981,247],{"class":206},[164,983,984],{"class":339},"color",[164,986,229],{"class":202},[164,988,989],{"class":173},"\"FFFFFF\"",[164,991,358],{"class":206},[164,993,994,997,999,1002,1005,1007,1010],{"class":63,"line":405},[164,995,996],{"class":206},"        cell.alignment ",[164,998,229],{"class":202},[164,1000,1001],{"class":206}," Alignment(",[164,1003,1004],{"class":339},"horizontal",[164,1006,229],{"class":202},[164,1008,1009],{"class":173},"\"center\"",[164,1011,358],{"class":206},[164,1013,1014,1016,1019,1021,1024,1027,1029,1032,1034,1037,1039,1041,1043,1046,1048,1050],{"class":63,"line":570},[164,1015,954],{"class":202},[164,1017,1018],{"class":206}," row ",[164,1020,497],{"class":202},[164,1022,1023],{"class":206}," sheet.iter_rows(",[164,1025,1026],{"class":339},"min_row",[164,1028,229],{"class":202},[164,1030,1031],{"class":301},"2",[164,1033,247],{"class":206},[164,1035,1036],{"class":339},"min_col",[164,1038,229],{"class":202},[164,1040,1031],{"class":301},[164,1042,247],{"class":206},[164,1044,1045],{"class":339},"max_col",[164,1047,229],{"class":202},[164,1049,1031],{"class":301},[164,1051,1052],{"class":206},"):\n",[164,1054,1055,1058,1060,1062],{"class":63,"line":587},[164,1056,1057],{"class":202},"        for",[164,1059,494],{"class":206},[164,1061,497],{"class":202},[164,1063,1064],{"class":206}," row:\n",[164,1066,1068,1071,1073],{"class":63,"line":1067},15,[164,1069,1070],{"class":206},"            cell.number_format ",[164,1072,229],{"class":202},[164,1074,567],{"class":173},[164,1076,1078,1080,1083,1085,1088,1091,1094,1096,1098],{"class":63,"line":1077},16,[164,1079,954],{"class":202},[164,1081,1082],{"class":206}," index, column ",[164,1084,497],{"class":202},[164,1086,1087],{"class":301}," enumerate",[164,1089,1090],{"class":206},"(summary.columns, ",[164,1092,1093],{"class":339},"start",[164,1095,229],{"class":202},[164,1097,503],{"class":301},[164,1099,1052],{"class":206},[164,1101,1103,1106,1108,1111,1114,1117,1119,1122,1125,1128,1130,1132,1134,1136,1139,1141,1144,1146,1149,1152],{"class":63,"line":1102},17,[164,1104,1105],{"class":206},"        width ",[164,1107,229],{"class":202},[164,1109,1110],{"class":301}," max",[164,1112,1113],{"class":206},"(",[164,1115,1116],{"class":301},"len",[164,1118,1113],{"class":206},[164,1120,1121],{"class":301},"str",[164,1123,1124],{"class":206},"(column)), ",[164,1126,1127],{"class":202},"*",[164,1129,1113],{"class":206},[164,1131,1116],{"class":301},[164,1133,1113],{"class":206},[164,1135,1121],{"class":301},[164,1137,1138],{"class":206},"(v)) ",[164,1140,491],{"class":202},[164,1142,1143],{"class":206}," v ",[164,1145,497],{"class":202},[164,1147,1148],{"class":206}," summary[column])) ",[164,1150,1151],{"class":202},"+",[164,1153,1154],{"class":301}," 4\n",[164,1156,1158,1161,1163],{"class":63,"line":1157},18,[164,1159,1160],{"class":206},"        sheet.column_dimensions[get_column_letter(index)].width ",[164,1162,229],{"class":202},[164,1164,1165],{"class":206}," width\n",[10,1167,1168,1171],{},[161,1169,1170],{},"writer.sheets"," hands you the live openpyxl worksheet, so the styling happens before the file is\nsaved rather than in a second pass. That single fact removes most of the reason to choose between\nthe two libraries at all.",[149,1173,1175],{"id":1174},"the-one-job-only-openpyxl-can-do","The one job only openpyxl can do",[10,1177,1178,1179,1181,1182,1185,1186,1189],{},"pandas cannot open an existing workbook and change part of it. ",[161,1180,601],{}," writes a new sheet; even\n",[161,1183,1184],{},"mode=\"a\""," on ",[161,1187,1188],{},"ExcelWriter"," appends a sheet rather than editing one, and it will not preserve\nformatting the way a real edit does. When the requirement is \"the finance team's template, with\nthis quarter's numbers in it\", the answer is openpyxl every time.",[154,1191,1193],{"className":193,"code":1192,"language":195,"meta":159,"style":159},"from openpyxl import load_workbook\n\nbook = load_workbook(\"template.xlsx\")     # logo, headers and formulas already in place\nsheet = book[\"Summary\"]\nsheet[\"C4\"] = 128400.0\nsheet[\"C5\"] = 96150.5\nbook.save(\"summary-2026-Q3.xlsx\")         # save under a new name, always\n",[161,1194,1195,1205,1209,1226,1239,1255,1269],{"__ignoreMap":159},[164,1196,1197,1199,1201,1203],{"class":63,"line":166},[164,1198,424],{"class":202},[164,1200,427],{"class":206},[164,1202,203],{"class":202},[164,1204,432],{"class":206},[164,1206,1207],{"class":63,"line":216},[164,1208,220],{"emptyLinePlaceholder":219},[164,1210,1211,1213,1215,1217,1220,1223],{"class":63,"line":223},[164,1212,453],{"class":206},[164,1214,229],{"class":202},[164,1216,458],{"class":206},[164,1218,1219],{"class":173},"\"template.xlsx\"",[164,1221,1222],{"class":206},")     ",[164,1224,1225],{"class":369},"# logo, headers and formulas already in place\n",[164,1227,1228,1230,1232,1234,1237],{"class":63,"line":235},[164,1229,467],{"class":206},[164,1231,229],{"class":202},[164,1233,472],{"class":206},[164,1235,1236],{"class":173},"\"Summary\"",[164,1238,477],{"class":206},[164,1240,1241,1244,1247,1250,1252],{"class":63,"line":265},[164,1242,1243],{"class":206},"sheet[",[164,1245,1246],{"class":173},"\"C4\"",[164,1248,1249],{"class":206},"] ",[164,1251,229],{"class":202},[164,1253,1254],{"class":301}," 128400.0\n",[164,1256,1257,1259,1262,1264,1266],{"class":63,"line":293},[164,1258,1243],{"class":206},[164,1260,1261],{"class":173},"\"C5\"",[164,1263,1249],{"class":206},[164,1265,229],{"class":202},[164,1267,1268],{"class":301}," 96150.5\n",[164,1270,1271,1273,1276,1279],{"class":63,"line":322},[164,1272,590],{"class":206},[164,1274,1275],{"class":173},"\"summary-2026-Q3.xlsx\"",[164,1277,1278],{"class":206},")         ",[164,1280,1281],{"class":369},"# save under a new name, always\n",[10,1283,1284,1288],{},[14,1285,1287],{"href":1286},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl\u002F","Fill an Excel Template with Python and openpyxl","\ndevelops that pattern; the important part here is that no amount of pandas gets you to it.",[149,1290,1292],{"id":1291},"five-real-requirements-decided","Five real requirements, decided",[10,1294,1295],{},"Requirements rarely arrive as \"should I use pandas or openpyxl\", so it helps to translate a few of\nthe common ones.",[10,1297,1298,1301,1302,1304],{},[800,1299,1300],{},"\"Total revenue by region and email the result.\""," pandas for the grouping, openpyxl only if the\nattached workbook needs to look designed. If a plain table will do, ",[161,1303,601],{}," alone is enough.",[10,1306,1307,1310],{},[800,1308,1309],{},"\"Update cell C4 in the board pack each month.\""," openpyxl alone. Loading, assigning and saving is\nthree lines, and pandas has no way to express it without rewriting the sheet.",[10,1312,1313,1316],{},[800,1314,1315],{},"\"Turn a 400,000-row export into a summary.\""," pandas for the aggregation, with the read pushed\nthrough a faster engine, and xlsxwriter for the output if it needs formatting. openpyxl's normal\nmode would be the slow path here.",[10,1318,1319,1322,1323,1327],{},[800,1320,1321],{},"\"Find every cell with a red fill and report it.\""," openpyxl alone — fills are a property of cells,\nand they are invisible to pandas. ",[14,1324,1326],{"href":1325},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fhighlight-invalid-cells-in-excel-with-python\u002F","Highlight Invalid Cells in Excel with Python","\nuses that same cell-level access in reverse.",[10,1329,1330,1333,1334,1338],{},[800,1331,1332],{},"\"Join two workbooks on account number and flag mismatches.\""," pandas, without hesitation. The\nequivalent in openpyxl means writing a join by hand, and\n",[14,1335,1337],{"href":1336},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-two-excel-files-on-common-column-python\u002F","Merge Two Excel Files on a Common Column in Python","\nshows how little code it takes with frames.",[10,1340,1341],{},"The pattern in all five: the library follows the noun in the requirement. Rows, totals and joins are\npandas nouns. Cells, fills, widths and templates are openpyxl nouns.",[149,1343,1345],{"id":1344},"what-each-one-costs-to-install","What each one costs to install",[10,1347,1348],{},"openpyxl is a pure-Python package with no heavy dependencies, so a container that only patches cells\nin a template can be small and fast to build. pandas pulls in NumPy and, in recent versions,\noptionally PyArrow — hundreds of megabytes once the wheels are unpacked. On a scheduled job that\nruns in a cold container every hour, that difference shows up in the startup time, not just the\nimage size.",[154,1350,1352],{"className":156,"code":1351,"language":158,"meta":159,"style":159},"pip install openpyxl          # ~250 KB wheel, no compiled dependencies\npip install pandas openpyxl   # pulls NumPy; considerably larger image\n",[161,1353,1354,1366],{"__ignoreMap":159},[164,1355,1356,1358,1360,1363],{"class":63,"line":166},[164,1357,170],{"class":169},[164,1359,174],{"class":173},[164,1361,1362],{"class":173}," openpyxl",[164,1364,1365],{"class":369},"          # ~250 KB wheel, no compiled dependencies\n",[164,1367,1368,1370,1372,1374,1376],{"class":63,"line":216},[164,1369,170],{"class":169},[164,1371,174],{"class":173},[164,1373,177],{"class":173},[164,1375,1362],{"class":173},[164,1377,1378],{"class":369},"   # pulls NumPy; considerably larger image\n",[10,1380,1381],{},"That is not an argument against pandas — a job that does real analysis needs it. It is an argument\nfor not installing it in the job that writes three cells into a template, which is a surprisingly\ncommon shape once reporting scripts multiply.",[149,1383,1385],{"id":1384},"common-pitfalls","Common pitfalls",[1387,1388,1389,1405],"table",{},[1390,1391,1392],"thead",{},[1393,1394,1395,1399,1402],"tr",{},[1396,1397,1398],"th",{},"Symptom",[1396,1400,1401],{},"Cause",[1396,1403,1404],{},"Fix",[1406,1407,1408,1425,1443,1458,1473],"tbody",{},[1393,1409,1410,1414,1419],{},[1411,1412,1413],"td",{},"Styling applied with openpyxl disappears",[1411,1415,1416,1417],{},"The file was rewritten afterwards with ",[161,1418,601],{},[1411,1420,1421,1422,1424],{},"Do the pandas write first, then style — or style through ",[161,1423,1170],{}," in one pass",[1393,1426,1427,1433,1436],{},[1411,1428,1429,1432],{},[161,1430,1431],{},"to_excel(mode=\"a\")"," raises on an existing sheet name",[1411,1434,1435],{},"pandas will not overwrite a sheet by default",[1411,1437,1438,1439,1442],{},"Pass ",[161,1440,1441],{},"if_sheet_exists=\"replace\"",", or edit with openpyxl instead",[1393,1444,1445,1448,1451],{},[1411,1446,1447],{},"Number formats look right in Python, wrong in Excel",[1411,1449,1450],{},"The value was written as a string",[1411,1452,1453,1454,1457],{},"Write real numbers and set ",[161,1455,1456],{},"number_format","; formatting a text cell changes nothing",[1393,1459,1460,1463,1466],{},[1411,1461,1462],{},"Memory spikes on a large file",[1411,1464,1465],{},"The full grid is materialised by both libraries",[1411,1467,1468,1469,1472],{},"Use ",[161,1470,1471],{},"read_only=True"," (openpyxl) or read in chunks; see the scale notes below",[1393,1474,1475,1478,1484],{},[1411,1476,1477],{},"Formulas read back as text",[1411,1479,1480,1481],{},"openpyxl returns the formula unless ",[161,1482,1483],{},"data_only=True",[1411,1485,1486,1487,1489],{},"Open with ",[161,1488,1483],{},", and remember only Excel populates cached values",[149,1491,1493],{"id":1492},"performance-and-scale","Performance and scale",[20,1495,29,1501,29,1504,29,1507,29,1511,29,1516,29,1523,29,1532,29,1538,29,1542,29,1545,29,1549,29,1554,29,1558,29,1560,29,1565,29,1570,29,1574],{"viewBox":1496,"role":23,"ariaLabelledBy":1497,"xmlns":27,"style":1500},"0 0 720 196",[1498,1499],"opd-scale-t","opd-scale-d","width:100%;max-width:720px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,1502,1503],{"id":1498},"Memory held per approach on the same large sheet",[35,1505,1506],{"id":1499},"openpyxl's normal mode holds one Python object per cell and uses the most memory, a pandas DataFrame holds one array per column and uses far less, and openpyxl in read-only mode holds a single row at a time.",[39,1508],{"x":41,"y":41,"width":1509,"height":1510,"fill":44},"720","196",[56,1512,1515],{"x":47,"y":1513,"style":1514},"56","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","openpyxl, normal",[39,1517],{"x":1518,"y":1519,"width":1520,"height":1521,"rx":1522,"fill":637,"stroke":638},"200","40","344.0","26","6",[39,1524],{"x":1525,"y":1526,"width":1527,"height":1528,"rx":1529,"fill":1530,"stroke":1531},"201","41","342.0","24","5","#fee8f2","var(--accent,#d81b73)",[56,1533,1537],{"x":1534,"y":1535,"style":1536},"556.0","58","font-size:12px;font-weight:700;fill:var(--accent,#d81b73);text-anchor:start","one object per cell",[56,1539,1541],{"x":47,"y":1540,"style":1514},"100","pandas DataFrame",[39,1543],{"x":1518,"y":1544,"width":1520,"height":1521,"rx":1522,"fill":637,"stroke":638},"84",[39,1546],{"x":1525,"y":1547,"width":1548,"height":1528,"rx":1529,"fill":52,"stroke":53},"85","113.8",[56,1550,1553],{"x":1534,"y":1551,"style":1552},"102","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","one array per column",[56,1555,1557],{"x":47,"y":1556,"style":1514},"144","openpyxl, read_only",[39,1559],{"x":1518,"y":116,"width":1520,"height":1521,"rx":1522,"fill":637,"stroke":638},[39,1561],{"x":1525,"y":1562,"width":48,"height":1528,"rx":1529,"fill":1563,"stroke":1564},"129","#fdefd8","var(--gold,#b4740a)",[56,1566,1569],{"x":1534,"y":1567,"style":1568},"146","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","one row at a time",[56,1571,1573],{"x":47,"y":47,"style":1572},"font-size:11.5px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:start","relative cost",[56,1575,1578],{"x":1576,"y":1577,"style":146},"360.0","186","for scanning, streaming wins; for holding, columns win",[10,1580,1581,1582,1584,1585,1589],{},"For a file under about 50,000 rows the difference is not worth measuring — both finish in under a\nsecond. Past that, three effects show up. pandas holds one NumPy array per column, which is far more\ncompact than openpyxl's one Python object per cell, so for reading a large sheet into memory pandas\nis the lighter of the two despite using openpyxl to parse it. For scanning a large sheet without\nkeeping it, openpyxl's ",[161,1583,1471],{}," mode wins outright because it never builds anything. And for\nwriting large volumes, neither is the right answer — that is xlsxwriter's territory, described in\n",[14,1586,1588],{"href":1587},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fbuilding-excel-reports-with-xlsxwriter\u002Fwrite-a-million-rows-to-excel-with-xlsxwriter-constant-memory\u002F","Write a Million Rows to Excel with XlsxWriter in Constant Memory",".",[154,1591,1593],{"className":193,"code":1592,"language":195,"meta":159,"style":159},"from openpyxl import load_workbook\n\n# Scan a big sheet without building a DataFrame or a cell grid.\nbook = load_workbook(\"large.xlsx\", read_only=True, data_only=True)\nsheet = book[\"Data\"]\nrows = 0\nfor row in sheet.iter_rows(min_row=2, values_only=True):\n    rows += 1\nbook.close()\nprint(f\"{rows:,} rows scanned\")\n",[161,1594,1595,1605,1609,1614,1645,1658,1668,1695,1706,1711],{"__ignoreMap":159},[164,1596,1597,1599,1601,1603],{"class":63,"line":166},[164,1598,424],{"class":202},[164,1600,427],{"class":206},[164,1602,203],{"class":202},[164,1604,432],{"class":206},[164,1606,1607],{"class":63,"line":216},[164,1608,220],{"emptyLinePlaceholder":219},[164,1610,1611],{"class":63,"line":223},[164,1612,1613],{"class":369},"# Scan a big sheet without building a DataFrame or a cell grid.\n",[164,1615,1616,1618,1620,1622,1625,1627,1630,1632,1634,1636,1639,1641,1643],{"class":63,"line":235},[164,1617,453],{"class":206},[164,1619,229],{"class":202},[164,1621,458],{"class":206},[164,1623,1624],{"class":173},"\"large.xlsx\"",[164,1626,247],{"class":206},[164,1628,1629],{"class":339},"read_only",[164,1631,229],{"class":202},[164,1633,524],{"class":301},[164,1635,247],{"class":206},[164,1637,1638],{"class":339},"data_only",[164,1640,229],{"class":202},[164,1642,524],{"class":301},[164,1644,358],{"class":206},[164,1646,1647,1649,1651,1653,1656],{"class":63,"line":265},[164,1648,467],{"class":206},[164,1650,229],{"class":202},[164,1652,472],{"class":206},[164,1654,1655],{"class":173},"\"Data\"",[164,1657,477],{"class":206},[164,1659,1660,1663,1665],{"class":63,"line":293},[164,1661,1662],{"class":206},"rows ",[164,1664,229],{"class":202},[164,1666,1667],{"class":301}," 0\n",[164,1669,1670,1672,1674,1676,1678,1680,1682,1684,1686,1689,1691,1693],{"class":63,"line":322},[164,1671,491],{"class":202},[164,1673,1018],{"class":206},[164,1675,497],{"class":202},[164,1677,1023],{"class":206},[164,1679,1026],{"class":339},[164,1681,229],{"class":202},[164,1683,1031],{"class":301},[164,1685,247],{"class":206},[164,1687,1688],{"class":339},"values_only",[164,1690,229],{"class":202},[164,1692,524],{"class":301},[164,1694,1052],{"class":206},[164,1696,1697,1700,1703],{"class":63,"line":328},[164,1698,1699],{"class":206},"    rows ",[164,1701,1702],{"class":202},"+=",[164,1704,1705],{"class":301}," 1\n",[164,1707,1708],{"class":63,"line":361},[164,1709,1710],{"class":206},"book.close()\n",[164,1712,1713,1715,1717,1720,1723,1727,1730,1733,1736,1739],{"class":63,"line":366},[164,1714,408],{"class":301},[164,1716,1113],{"class":206},[164,1718,1719],{"class":202},"f",[164,1721,1722],{"class":173},"\"",[164,1724,1726],{"class":1725},"sSjpA","{",[164,1728,1729],{"class":206},"rows",[164,1731,1732],{"class":202},":,",[164,1734,1735],{"class":1725},"}",[164,1737,1738],{"class":173}," rows scanned\"",[164,1740,358],{"class":206},[149,1742,1744],{"id":1743},"conclusion","Conclusion",[10,1746,1747,1748,1750],{},"Use pandas for the data and openpyxl for the document. pandas turns a sheet into a typed table and\nback again in two lines, and knows nothing about how the result looks; openpyxl controls every\nvisible property of the file and is the only one of the two that can edit a workbook that already\nexists. The productive pattern is not to choose but to sequence them — aggregate with pandas, then\nreach through ",[161,1749,1170],{}," and finish the sheet with openpyxl before it is saved.",[149,1752,1754],{"id":1753},"frequently-asked-questions","Frequently asked questions",[10,1756,1757,1761],{},[1758,1759,1760],"strong",{},"Is openpyxl faster than pandas for reading Excel?","\nNo — pandas uses openpyxl to do the reading, so it cannot be faster than what it delegates to. The only case where openpyxl alone wins is a streaming read with read_only=True, where you scan rows without ever building a DataFrame.",[10,1763,1764,1767],{},[1758,1765,1766],{},"Can I use both in the same script?","\nYes, and most production scripts do. Read and reshape with pandas, then reopen the saved file with openpyxl to add widths, styles or a formula column. pandas.ExcelWriter even exposes the underlying openpyxl workbook as writer.book so you can do it without reopening.",[10,1769,1770,1773],{},[1758,1771,1772],{},"Why does my pandas output have an extra unnamed first column?","\nto_excel writes the DataFrame index by default. Pass index=False whenever the row numbers are not meaningful data.",[10,1775,1776,1779],{},[1758,1777,1778],{},"Does openpyxl need pandas installed?","\nNo. openpyxl is standalone and has no dependency on pandas or NumPy, which makes it the lighter choice for a container that only patches cells in an existing workbook.",[149,1781,1783],{"id":1782},"related","Related",[1785,1786,1787,1794,1801,1808,1815],"ul",{},[1788,1789,1790,1791,1793],"li",{},"Up one level: ",[14,1792,17],{"href":16}," — the whole landscape, including xlsxwriter, Polars and xlwings.",[1788,1795,1796,1800],{},[14,1797,1799],{"href":1798},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fopenpyxl-vs-xlsxwriter-vs-pandas-excelwriter\u002F","openpyxl vs xlsxwriter vs pandas.ExcelWriter"," — the same question on the writing side.",[1788,1802,1803,1807],{},[14,1804,1806],{"href":1805},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002F","Using openpyxl for Excel File Manipulation"," — the cell-level API in depth.",[1788,1809,1810,1814],{},[14,1811,1813],{"href":1812},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002F","Writing DataFrames to Excel with Pandas"," — the pandas half of the handoff.",[1788,1816,1817,1821],{},[14,1818,1820],{"href":1819},"\u002Fgetting-started-with-python-excel-automation\u002Fwriting-dataframes-to-excel-with-pandas\u002Fauto-fit-column-widths-when-writing-with-pandas\u002F","Auto-Fit Column Widths When Writing with Pandas"," — the most common reason to reach through to openpyxl.",[1823,1824,1825],"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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":159,"searchDepth":216,"depth":216,"links":1827},[1828,1829,1830,1831,1832,1833,1834,1835,1836,1837,1838,1839],{"id":151,"depth":216,"text":152},{"id":186,"depth":216,"text":187},{"id":605,"depth":216,"text":606},{"id":806,"depth":216,"text":807},{"id":1174,"depth":216,"text":1175},{"id":1291,"depth":216,"text":1292},{"id":1344,"depth":216,"text":1345},{"id":1384,"depth":216,"text":1385},{"id":1492,"depth":216,"text":1493},{"id":1743,"depth":216,"text":1744},{"id":1753,"depth":216,"text":1754},{"id":1782,"depth":216,"text":1783},"2026-09-04","pandas reads Excel through openpyxl, so they are not rivals. See where the line falls — rows versus cells — and how to hand off between them in one script.","md",[1844,1846,1848,1850],{"q":1760,"a":1845},"No — pandas uses openpyxl to do the reading, so it cannot be faster than what it delegates to. The only case where openpyxl alone wins is a streaming read with read_only=True, where you scan rows without ever building a DataFrame.",{"q":1766,"a":1847},"Yes, and most production scripts do. Read and reshape with pandas, then reopen the saved file with openpyxl to add widths, styles or a formula column. pandas.ExcelWriter even exposes the underlying openpyxl workbook as writer.book so you can do it without reopening.",{"q":1772,"a":1849},"to_excel writes the DataFrame index by default. Pass index=False whenever the row numbers are not meaningful data.",{"q":1778,"a":1851},"No. openpyxl is standalone and has no dependency on pandas or NumPy, which makes it the lighter choice for a container that only patches cells in an existing workbook.",{"breadcrumb":1853},[1854,1857,1860],{"name":1855,"item":1856},"Home","\u002F",{"name":1858,"item":1859},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":17,"item":16},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation",{"title":5,"description":1863},"pandas shapes the data, openpyxl shapes the file. Compare reading, writing, editing and memory, and use pandas.ExcelWriter to combine both in one pass.","openpyxl-vs-pandas-for-excel-automation","getting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fopenpyxl-vs-pandas-for-excel-automation\u002Findex","how-to","zhcUUr7EWAb01C22fGTr15tL_tgLqLoOzyZgdXQvqZw",[1869,1873],{"title":1870,"path":1871,"stem":1872,"children":-1},"Excel vs CSV vs Parquet for Python Data Pipelines","\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fexcel-vs-csv-vs-parquet-for-python-data-pipelines","getting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fexcel-vs-csv-vs-parquet-for-python-data-pipelines\u002Findex",{"title":1874,"path":1875,"stem":1876,"children":-1},"pandas vs Polars for Excel Workflows","\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpandas-vs-polars-for-excel-workflows","getting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpandas-vs-polars-for-excel-workflows\u002Findex",1788710154635]