[{"data":1,"prerenderedAt":1509},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-an-excel-file-with-polars-read-excel":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-an-excel-file-with-polars-read-excel":1500},{"id":4,"title":5,"body":6,"dateModified":1475,"datePublished":1475,"description":1476,"extension":1477,"faq":1478,"meta":1491,"navigation":211,"path":1492,"seo":1493,"slug":1496,"stem":1497,"type":1498,"__hash__":1499},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-an-excel-file-with-polars-read-excel\u002Findex.md","Read an Excel File with polars.read_excel",{"type":7,"value":8,"toc":1459},"minimark",[9,27,130,135,160,179,183,257,264,268,271,421,434,438,445,531,544,548,555,650,669,753,756,828,838,842,848,923,930,934,937,965,975,979,982,1017,1031,1092,1095,1099,1102,1209,1219,1223,1305,1309,1322,1326,1344,1348,1371,1384,1394,1407,1413,1417,1455],[10,11,12,16,17,20,21,26],"p",{},[13,14,15],"code",{},"polars.read_excel()"," is the fastest simple way to get a spreadsheet into a DataFrame: it parses through the Rust ",[13,18,19],{},"calamine"," reader and produces Arrow-backed columns ready for a multi-threaded query engine. The API is close enough to pandas that porting is mostly mechanical, but the type handling is stricter — which is a feature once you know where to declare types. This guide covers every argument you will actually use, plus the traps that bite when a real workbook meets a strict reader. It belongs to ",[22,23,25],"a",{"href":24},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002F","Reading Excel with Polars and Arrow",".",[28,29,37,38,37,42,37,46,37,53,37,60,37,69,37,75,37,80,37,84,37,88,37,92,37,97,37,100,37,103,37,106,37,111,37,116,37,119,37,122,37,126],"svg",{"viewBox":30,"role":31,"ariaLabelledBy":32,"xmlns":35,"style":36},"0 0 760 232","img",[33,34],"pre-t","pre-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  ",[39,40,41],"title",{"id":33},"The arguments that shape a read_excel call",[43,44,45],"desc",{"id":34},"Sheet selection chooses what to read, read_options control the header and skipped rows, and schema_overrides decide the column types before any data is materialised.",[47,48],"rect",{"x":49,"y":49,"width":50,"height":51,"fill":52},"0","760","232","#ffffff",[54,55,59],"text",{"x":56,"y":57,"style":58},"380","26","font-size:13px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","Three decisions, made once, at the read",[47,61],{"x":62,"y":63,"width":64,"height":65,"rx":66,"fill":67,"stroke":68},"28","46","222","132","12","#ebebfd","var(--line,#cdd5e6)",[54,70,74],{"x":71,"y":72,"style":73},"139","76","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","which sheet",[54,76,79],{"x":71,"y":77,"style":78},"106","font-size:11.5px;fill:var(--text,#172033);text-anchor:middle","sheet_name=\"Q3\"",[54,81,83],{"x":71,"y":82,"style":78},"130","sheet_id=2",[54,85,87],{"x":71,"y":86,"style":78},"154","sheet_id=None → dict",[47,89],{"x":90,"y":63,"width":64,"height":65,"rx":66,"fill":91,"stroke":68},"266","#d9f4f1",[54,93,96],{"x":94,"y":72,"style":95},"377","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","where the data starts",[54,98,99],{"x":94,"y":77,"style":78},"read_options=",[54,101,102],{"x":94,"y":82,"style":78},"{\"header_row\": 2}",[54,104,105],{"x":94,"y":86,"style":78},"{\"n_rows\": 5000}",[47,107],{"x":108,"y":63,"width":109,"height":65,"rx":66,"fill":110,"stroke":68},"504","228","#fdefd8",[54,112,115],{"x":113,"y":72,"style":114},"618","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","what the types are",[54,117,118],{"x":113,"y":77,"style":78},"schema_overrides=",[54,120,121],{"x":113,"y":82,"style":78},"{\"order_id\": pl.String}",[54,123,125],{"x":113,"y":86,"style":124},"font-size:11.5px;fill:var(--muted,#5b6780);text-anchor:middle","declared, not repaired",[54,127,129],{"x":56,"y":128,"style":124},"212","Getting these right at the read removes most downstream cleaning",[131,132,134],"h2",{"id":133},"prerequisites","Prerequisites",[136,137,142],"pre",{"className":138,"code":139,"language":140,"meta":141,"style":141},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install \"polars[excel]\"\n","bash","",[13,143,144],{"__ignoreMap":141},[145,146,149,153,157],"span",{"class":147,"line":148},"line",1,[145,150,152],{"class":151},"sMTad","pip",[145,154,156],{"class":155},"srMev"," install",[145,158,159],{"class":155}," \"polars[excel]\"\n",[10,161,162,163,166,167,170,171,174,175,178],{},"That pulls ",[13,164,165],{},"polars"," plus ",[13,168,169],{},"fastexcel",", the binding over the calamine parser. Add ",[13,172,173],{},"pyarrow"," if you plan to convert to pandas or write Parquet, and ",[13,176,177],{},"xlsxwriter"," if you will write workbooks back out.",[131,180,182],{"id":181},"the-basic-read","The basic read",[136,184,188],{"className":185,"code":186,"language":187,"meta":141,"style":141},"language-python shiki shiki-themes github-light github-dark-high-contrast","import polars as pl\n\ndf = pl.read_excel(\"sales.xlsx\")\nprint(df.head())\nprint(df.schema)\nprint(df.shape)\n","python",[13,189,190,206,213,231,241,249],{"__ignoreMap":141},[145,191,192,196,200,203],{"class":147,"line":148},[145,193,195],{"class":194},"s-kum","import",[145,197,199],{"class":198},"skGVy"," polars ",[145,201,202],{"class":194},"as",[145,204,205],{"class":198}," pl\n",[145,207,209],{"class":147,"line":208},2,[145,210,212],{"emptyLinePlaceholder":211},true,"\n",[145,214,216,219,222,225,228],{"class":147,"line":215},3,[145,217,218],{"class":198},"df ",[145,220,221],{"class":194},"=",[145,223,224],{"class":198}," pl.read_excel(",[145,226,227],{"class":155},"\"sales.xlsx\"",[145,229,230],{"class":198},")\n",[145,232,234,238],{"class":147,"line":233},4,[145,235,237],{"class":236},"sP0c6","print",[145,239,240],{"class":198},"(df.head())\n",[145,242,244,246],{"class":147,"line":243},5,[145,245,237],{"class":236},[145,247,248],{"class":198},"(df.schema)\n",[145,250,252,254],{"class":147,"line":251},6,[145,253,237],{"class":236},[145,255,256],{"class":198},"(df.shape)\n",[10,258,259,260,263],{},"With no arguments the first sheet is read and the first row becomes the header. ",[13,261,262],{},"schema"," prints the inferred type of every column, and it is worth a look on any new file — the types decided here follow the data through the rest of the job.",[131,265,267],{"id":266},"choose-the-sheet","Choose the sheet",[10,269,270],{},"Three forms cover everything:",[136,272,274],{"className":185,"code":273,"language":187,"meta":141,"style":141},"by_name = pl.read_excel(\"sales.xlsx\", sheet_name=\"Q3 Summary\")\nby_index = pl.read_excel(\"sales.xlsx\", sheet_id=2)          # 1-based\nevery = pl.read_excel(\"sales.xlsx\", sheet_id=None)          # dict of DataFrames\n\nfor name, frame in every.items():\n    print(f\"{name}: {frame.height} rows x {frame.width} cols\")\n",[13,275,276,301,329,354,358,372],{"__ignoreMap":141},[145,277,278,281,283,285,287,290,294,296,299],{"class":147,"line":148},[145,279,280],{"class":198},"by_name ",[145,282,221],{"class":194},[145,284,224],{"class":198},[145,286,227],{"class":155},[145,288,289],{"class":198},", ",[145,291,293],{"class":292},"sa561","sheet_name",[145,295,221],{"class":194},[145,297,298],{"class":155},"\"Q3 Summary\"",[145,300,230],{"class":198},[145,302,303,306,308,310,312,314,317,319,322,325],{"class":147,"line":208},[145,304,305],{"class":198},"by_index ",[145,307,221],{"class":194},[145,309,224],{"class":198},[145,311,227],{"class":155},[145,313,289],{"class":198},[145,315,316],{"class":292},"sheet_id",[145,318,221],{"class":194},[145,320,321],{"class":236},"2",[145,323,324],{"class":198},")          ",[145,326,328],{"class":327},"s-wDw","# 1-based\n",[145,330,331,334,336,338,340,342,344,346,349,351],{"class":147,"line":215},[145,332,333],{"class":198},"every ",[145,335,221],{"class":194},[145,337,224],{"class":198},[145,339,227],{"class":155},[145,341,289],{"class":198},[145,343,316],{"class":292},[145,345,221],{"class":194},[145,347,348],{"class":236},"None",[145,350,324],{"class":198},[145,352,353],{"class":327},"# dict of DataFrames\n",[145,355,356],{"class":147,"line":233},[145,357,212],{"emptyLinePlaceholder":211},[145,359,360,363,366,369],{"class":147,"line":243},[145,361,362],{"class":194},"for",[145,364,365],{"class":198}," name, frame ",[145,367,368],{"class":194},"in",[145,370,371],{"class":198}," every.items():\n",[145,373,374,377,380,383,386,390,393,396,399,401,404,406,409,411,414,416,419],{"class":147,"line":251},[145,375,376],{"class":236},"    print",[145,378,379],{"class":198},"(",[145,381,382],{"class":194},"f",[145,384,385],{"class":155},"\"",[145,387,389],{"class":388},"sSjpA","{",[145,391,392],{"class":198},"name",[145,394,395],{"class":388},"}",[145,397,398],{"class":155},": ",[145,400,389],{"class":388},[145,402,403],{"class":198},"frame.height",[145,405,395],{"class":388},[145,407,408],{"class":155}," rows x ",[145,410,389],{"class":388},[145,412,413],{"class":198},"frame.width",[145,415,395],{"class":388},[145,417,418],{"class":155}," cols\"",[145,420,230],{"class":198},[10,422,423,425,426,429,430,26],{},[13,424,316],{}," is one-based, unlike pandas' zero-based ",[13,427,428],{},"sheet_name=0"," — the most common porting slip. If a name might not exist, check first rather than catching an exception, and remember that sheet names carry invisible whitespace surprisingly often — see ",[22,431,433],{"href":432},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-worksheet-does-not-exist-keyerror-in-openpyxl\u002F","Fix \"Worksheet does not exist\" KeyError in openpyxl",[131,435,437],{"id":436},"skip-banner-rows-and-limit-what-you-read","Skip banner rows and limit what you read",[10,439,440,441,444],{},"Real workbooks rarely start with the header in row 1. ",[13,442,443],{},"read_options"," passes settings straight through to the underlying reader:",[136,446,448],{"className":185,"code":447,"language":187,"meta":141,"style":141},"df = pl.read_excel(\n    \"sales.xlsx\",\n    read_options={\n        \"header_row\": 3,        # zero-based: row 4 in Excel holds the headers\n        \"skip_rows\": 0,\n        \"n_rows\": 10_000,       # read a sample while developing\n    },\n)\n",[13,449,450,459,467,477,493,504,520,526],{"__ignoreMap":141},[145,451,452,454,456],{"class":147,"line":148},[145,453,218],{"class":198},[145,455,221],{"class":194},[145,457,458],{"class":198}," pl.read_excel(\n",[145,460,461,464],{"class":147,"line":208},[145,462,463],{"class":155},"    \"sales.xlsx\"",[145,465,466],{"class":198},",\n",[145,468,469,472,474],{"class":147,"line":215},[145,470,471],{"class":292},"    read_options",[145,473,221],{"class":194},[145,475,476],{"class":198},"{\n",[145,478,479,482,484,487,490],{"class":147,"line":233},[145,480,481],{"class":155},"        \"header_row\"",[145,483,398],{"class":198},[145,485,486],{"class":236},"3",[145,488,489],{"class":198},",        ",[145,491,492],{"class":327},"# zero-based: row 4 in Excel holds the headers\n",[145,494,495,498,500,502],{"class":147,"line":243},[145,496,497],{"class":155},"        \"skip_rows\"",[145,499,398],{"class":198},[145,501,49],{"class":236},[145,503,466],{"class":198},[145,505,506,509,511,514,517],{"class":147,"line":251},[145,507,508],{"class":155},"        \"n_rows\"",[145,510,398],{"class":198},[145,512,513],{"class":236},"10_000",[145,515,516],{"class":198},",       ",[145,518,519],{"class":327},"# read a sample while developing\n",[145,521,523],{"class":147,"line":522},7,[145,524,525],{"class":198},"    },\n",[145,527,529],{"class":147,"line":528},8,[145,530,230],{"class":198},[10,532,533,536,537,540,541,26],{},[13,534,535],{},"n_rows"," is the fastest way to iterate on a transformation against a huge file: read ten thousand rows, get the pipeline right, then remove the argument. If the sheet has no header at all, pass ",[13,538,539],{},"has_header: False"," and name the columns afterwards with ",[13,542,543],{},"df.columns = [...]",[131,545,547],{"id":546},"declare-the-types-instead-of-repairing-them","Declare the types instead of repairing them",[10,549,550,551,554],{},"Type inference is the one place a strict reader creates work, and ",[13,552,553],{},"schema_overrides"," is the answer. It is applied during the read, so nothing is ever materialised with the wrong type:",[136,556,558],{"className":185,"code":557,"language":187,"meta":141,"style":141},"import polars as pl\n\ndf = pl.read_excel(\n    \"sales.xlsx\",\n    schema_overrides={\n        \"order_id\": pl.String,        # keep leading zeros\n        \"revenue\": pl.Float64,\n        \"order_date\": pl.Date,\n        \"quantity\": pl.Int32,\n    },\n)\nprint(df.schema)\n",[13,559,560,570,574,582,588,597,608,616,624,633,638,643],{"__ignoreMap":141},[145,561,562,564,566,568],{"class":147,"line":148},[145,563,195],{"class":194},[145,565,199],{"class":198},[145,567,202],{"class":194},[145,569,205],{"class":198},[145,571,572],{"class":147,"line":208},[145,573,212],{"emptyLinePlaceholder":211},[145,575,576,578,580],{"class":147,"line":215},[145,577,218],{"class":198},[145,579,221],{"class":194},[145,581,458],{"class":198},[145,583,584,586],{"class":147,"line":233},[145,585,463],{"class":155},[145,587,466],{"class":198},[145,589,590,593,595],{"class":147,"line":243},[145,591,592],{"class":292},"    schema_overrides",[145,594,221],{"class":194},[145,596,476],{"class":198},[145,598,599,602,605],{"class":147,"line":251},[145,600,601],{"class":155},"        \"order_id\"",[145,603,604],{"class":198},": pl.String,        ",[145,606,607],{"class":327},"# keep leading zeros\n",[145,609,610,613],{"class":147,"line":522},[145,611,612],{"class":155},"        \"revenue\"",[145,614,615],{"class":198},": pl.Float64,\n",[145,617,618,621],{"class":147,"line":528},[145,619,620],{"class":155},"        \"order_date\"",[145,622,623],{"class":198},": pl.Date,\n",[145,625,627,630],{"class":147,"line":626},9,[145,628,629],{"class":155},"        \"quantity\"",[145,631,632],{"class":198},": pl.Int32,\n",[145,634,636],{"class":147,"line":635},10,[145,637,525],{"class":198},[145,639,641],{"class":147,"line":640},11,[145,642,230],{"class":198},[145,644,646,648],{"class":147,"line":645},12,[145,647,237],{"class":236},[145,649,248],{"class":198},[10,651,652,653,656,657,660,661,664,665,668],{},"Three columns benefit almost universally. Identifiers — order numbers, product codes, postcodes — must be strings or leading zeros vanish. Money should be an explicit ",[13,654,655],{},"Float64"," (or ",[13,658,659],{},"Decimal"," where exactness matters) so a column of whole numbers in one file and decimals in another does not change type between runs. And dates should be ",[13,662,663],{},"pl.Date"," or ",[13,666,667],{},"pl.Datetime"," so later date arithmetic works without a cast.",[28,670,37,675,37,678,37,681,37,684,37,688,37,692,37,700,37,705,37,710,37,714,37,720,37,723,37,727,37,731,37,733,37,736,37,739,37,743,37,747,37,749],{"viewBox":671,"role":31,"ariaLabelledBy":672,"xmlns":35,"style":36},"0 0 760 226",[673,674],"pre2-t","pre2-d",[39,676,677],{"id":673},"What inference does to three common columns",[43,679,680],{"id":674},"Left to inference an order id loses leading zeros, a mixed money column becomes a string, and a date stays a serial number; declaring the schema fixes all three at the read.",[47,682],{"x":49,"y":49,"width":50,"height":683,"fill":52},"226",[54,685,687],{"x":64,"y":62,"style":686},"font-size:12.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","inferred",[54,689,691],{"x":690,"y":62,"style":95},"576","declared",[47,693],{"x":694,"y":695,"width":696,"height":697,"rx":698,"fill":699,"stroke":68},"30","44","384","50","9","#fee8f2",[54,701,704],{"x":64,"y":702,"style":703},"66","font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","order_id \"00417\" → 417 (Int64)",[54,706,709],{"x":64,"y":707,"style":708},"86","font-size:11px;fill:var(--muted,#5b6780);text-anchor:middle","leading zeros gone, joins now miss",[47,711],{"x":712,"y":695,"width":713,"height":697,"rx":698,"fill":91,"stroke":68},"428","302",[54,715,719],{"x":716,"y":717,"style":718},"579","74","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","pl.String keeps \"00417\"",[47,721],{"x":694,"y":722,"width":696,"height":697,"rx":698,"fill":699,"stroke":68},"102",[54,724,726],{"x":64,"y":725,"style":703},"124","revenue \"1,234.50\" → String",[54,728,730],{"x":64,"y":729,"style":708},"144","sums silently concatenate or fail",[47,732],{"x":712,"y":722,"width":713,"height":697,"rx":698,"fill":91,"stroke":68},[54,734,735],{"x":716,"y":65,"style":718},"strip commas, cast to Float64",[47,737],{"x":694,"y":738,"width":696,"height":697,"rx":698,"fill":699,"stroke":68},"160",[54,740,742],{"x":64,"y":741,"style":703},"182","order_date 45900 → Int64",[54,744,746],{"x":64,"y":745,"style":708},"202","an Excel serial, not a date",[47,748],{"x":712,"y":738,"width":713,"height":697,"rx":698,"fill":91,"stroke":68},[54,750,752],{"x":716,"y":751,"style":718},"190","pl.Date parses on the way in",[10,754,755],{},"When a column arrives as formatted text, clean and cast in one expression after the read:",[136,757,759],{"className":185,"code":758,"language":187,"meta":141,"style":141},"df = df.with_columns(\n    pl.col(\"revenue\").cast(pl.String)\n      .str.replace_all(r\"[,$£€\\s]\", \"\")\n      .cast(pl.Float64, strict=False)\n)\n",[13,760,761,770,781,809,824],{"__ignoreMap":141},[145,762,763,765,767],{"class":147,"line":148},[145,764,218],{"class":198},[145,766,221],{"class":194},[145,768,769],{"class":198}," df.with_columns(\n",[145,771,772,775,778],{"class":147,"line":208},[145,773,774],{"class":198},"    pl.col(",[145,776,777],{"class":155},"\"revenue\"",[145,779,780],{"class":198},").cast(pl.String)\n",[145,782,783,786,789,791,794,797,800,802,804,807],{"class":147,"line":215},[145,784,785],{"class":198},"      .str.replace_all(",[145,787,788],{"class":194},"r",[145,790,385],{"class":155},[145,792,793],{"class":236},"[",[145,795,796],{"class":388},",$£€",[145,798,799],{"class":236},"\\s]",[145,801,385],{"class":155},[145,803,289],{"class":198},[145,805,806],{"class":155},"\"\"",[145,808,230],{"class":198},[145,810,811,814,817,819,822],{"class":147,"line":233},[145,812,813],{"class":198},"      .cast(pl.Float64, ",[145,815,816],{"class":292},"strict",[145,818,221],{"class":194},[145,820,821],{"class":236},"False",[145,823,230],{"class":198},[145,825,826],{"class":147,"line":243},[145,827,230],{"class":198},[10,829,830,833,834,837],{},[13,831,832],{},"strict=False"," turns unparseable values into nulls instead of raising — the right behaviour when one stray footnote should not abort a nightly job. Count them afterwards with ",[13,835,836],{},"df[\"revenue\"].null_count()"," so a silent data problem still gets reported.",[131,839,841],{"id":840},"read-from-bytes-not-just-from-disk","Read from bytes, not just from disk",[10,843,844,847],{},[13,845,846],{},"read_excel"," accepts any file-like object, which makes downloads and uploads straightforward:",[136,849,851],{"className":185,"code":850,"language":187,"meta":141,"style":141},"import io\n\nimport polars as pl\nimport requests\n\nresp = requests.get(\"https:\u002F\u002Fexample.com\u002Freports\u002Flatest.xlsx\", timeout=30)\nresp.raise_for_status()\ndf = pl.read_excel(io.BytesIO(resp.content))\n",[13,852,853,860,864,874,881,885,909,914],{"__ignoreMap":141},[145,854,855,857],{"class":147,"line":148},[145,856,195],{"class":194},[145,858,859],{"class":198}," io\n",[145,861,862],{"class":147,"line":208},[145,863,212],{"emptyLinePlaceholder":211},[145,865,866,868,870,872],{"class":147,"line":215},[145,867,195],{"class":194},[145,869,199],{"class":198},[145,871,202],{"class":194},[145,873,205],{"class":198},[145,875,876,878],{"class":147,"line":233},[145,877,195],{"class":194},[145,879,880],{"class":198}," requests\n",[145,882,883],{"class":147,"line":243},[145,884,212],{"emptyLinePlaceholder":211},[145,886,887,890,892,895,898,900,903,905,907],{"class":147,"line":251},[145,888,889],{"class":198},"resp ",[145,891,221],{"class":194},[145,893,894],{"class":198}," requests.get(",[145,896,897],{"class":155},"\"https:\u002F\u002Fexample.com\u002Freports\u002Flatest.xlsx\"",[145,899,289],{"class":198},[145,901,902],{"class":292},"timeout",[145,904,221],{"class":194},[145,906,694],{"class":236},[145,908,230],{"class":198},[145,910,911],{"class":147,"line":522},[145,912,913],{"class":198},"resp.raise_for_status()\n",[145,915,916,918,920],{"class":147,"line":528},[145,917,218],{"class":198},[145,919,221],{"class":194},[145,921,922],{"class":198}," pl.read_excel(io.BytesIO(resp.content))\n",[10,924,925,926,26],{},"The same applies to a file object from a web framework's upload handling, or to bytes pulled from object storage. If the response might not be a workbook at all, check it first — the failure modes are catalogued in ",[22,927,929],{"href":928},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-excel-file-format-cannot-be-determined-in-pandas\u002F","Fix \"Excel file format cannot be determined\" in pandas",[131,931,933],{"id":932},"hand-off-to-pandas-when-it-suits-you","Hand off to pandas when it suits you",[10,935,936],{},"Conversion runs through Arrow and is cheap enough to do casually:",[136,938,940],{"className":185,"code":939,"language":187,"meta":141,"style":141},"pdf = df.to_pandas()             # for existing formatting or plotting code\nagain = pl.from_pandas(pdf)\n",[13,941,942,955],{"__ignoreMap":141},[145,943,944,947,949,952],{"class":147,"line":148},[145,945,946],{"class":198},"pdf ",[145,948,221],{"class":194},[145,950,951],{"class":198}," df.to_pandas()             ",[145,953,954],{"class":327},"# for existing formatting or plotting code\n",[145,956,957,960,962],{"class":147,"line":208},[145,958,959],{"class":198},"again ",[145,961,221],{"class":194},[145,963,964],{"class":198}," pl.from_pandas(pdf)\n",[10,966,967,968,971,972,974],{},"This is what makes adoption incremental: read and reshape with Polars where the file is large, then convert and reuse whatever openpyxl or xlsxwriter code you already have. ",[13,969,970],{},"to_pandas()"," needs ",[13,973,173],{}," installed.",[131,976,978],{"id":977},"inspect-the-frame-before-trusting-it","Inspect the frame before trusting it",[10,980,981],{},"A read that raises no error can still be wrong: the header row was off by one, a merged title cell became a column of nulls, or a footer row of totals is now a data row. Three cheap calls catch nearly all of it:",[136,983,985],{"className":185,"code":984,"language":187,"meta":141,"style":141},"print(df.glimpse())          # every column, its type, and the first values\nprint(df.null_count())       # nulls per column — a solid wall means a wrong header row\nprint(df.describe())         # min\u002Fmax\u002Fmean, which exposes totals rows and stray magnitudes\n",[13,986,987,997,1007],{"__ignoreMap":141},[145,988,989,991,994],{"class":147,"line":148},[145,990,237],{"class":236},[145,992,993],{"class":198},"(df.glimpse())          ",[145,995,996],{"class":327},"# every column, its type, and the first values\n",[145,998,999,1001,1004],{"class":147,"line":208},[145,1000,237],{"class":236},[145,1002,1003],{"class":198},"(df.null_count())       ",[145,1005,1006],{"class":327},"# nulls per column — a solid wall means a wrong header row\n",[145,1008,1009,1011,1014],{"class":147,"line":215},[145,1010,237],{"class":236},[145,1012,1013],{"class":198},"(df.describe())         ",[145,1015,1016],{"class":327},"# min\u002Fmax\u002Fmean, which exposes totals rows and stray magnitudes\n",[10,1018,1019,1022,1023,1026,1027,1030],{},[13,1020,1021],{},"describe()"," is the one that finds the footer: a ",[13,1024,1025],{},"max"," of exactly the sum of the column means a totals row slipped into the data. ",[13,1028,1029],{},"null_count()"," finds the opposite problem — a column that is entirely null usually means the header row was mis-set and the real names are sitting in row 1 as data.",[28,1032,37,1037,37,1040,37,1043,37,1046,37,1049,37,1052,37,1057,37,1061,37,1065,37,1068,37,1071,37,1074,37,1077,37,1081,37,1083,37,1086,37,1089],{"viewBox":1033,"role":31,"ariaLabelledBy":1034,"xmlns":35,"style":36},"0 0 760 206",[1035,1036],"pre3-t","pre3-d",[39,1038,1039],{"id":1035},"Three checks and the defect each one finds",[43,1041,1042],{"id":1036},"glimpse reveals wrong types, null_count reveals a mis-set header row, and describe reveals a totals row that was read as data.",[47,1044],{"x":49,"y":49,"width":50,"height":1045,"fill":52},"206",[54,1047,1048],{"x":56,"y":57,"style":58},"A three-line audit after every new file",[47,1050],{"x":694,"y":63,"width":64,"height":1051,"rx":66,"fill":67,"stroke":68},"118",[54,1053,1056],{"x":1054,"y":1055,"style":73},"141","78","glimpse()",[54,1058,1060],{"x":1054,"y":1059,"style":78},"108","types and sample values",[54,1062,1064],{"x":1054,"y":1063,"style":124},"136","finds: a numeric id",[47,1066],{"x":1067,"y":63,"width":64,"height":1051,"rx":66,"fill":91,"stroke":68},"268",[54,1069,1029],{"x":1070,"y":1055,"style":95},"379",[54,1072,1073],{"x":1070,"y":1059,"style":78},"nulls per column",[54,1075,1076],{"x":1070,"y":1063,"style":124},"finds: wrong header row",[47,1078],{"x":1079,"y":63,"width":1080,"height":1051,"rx":66,"fill":110,"stroke":68},"506","224",[54,1082,1021],{"x":113,"y":1055,"style":114},[54,1084,1085],{"x":113,"y":1059,"style":78},"min, max, mean",[54,1087,1088],{"x":113,"y":1063,"style":124},"finds: a totals row",[54,1090,1091],{"x":56,"y":751,"style":124},"Each of these has shipped a wrong report at least once",[10,1093,1094],{},"Keep the three calls behind a verbose flag in a scheduled job so a suspicious run can be re-run with the audit on, without printing a wall of statistics on every ordinary night.",[131,1096,1098],{"id":1097},"read-many-sheets-into-one-frame","Read many sheets into one frame",[10,1100,1101],{},"A workbook with one sheet per month is common, and stacking them is a two-line job once you tag each frame with where it came from:",[136,1103,1105],{"className":185,"code":1104,"language":187,"meta":141,"style":141},"import polars as pl\n\nsheets = pl.read_excel(\"year.xlsx\", sheet_id=None)\ncombined = pl.concat(\n    [frame.with_columns(sheet=pl.lit(name)) for name, frame in sheets.items()],\n    how=\"diagonal_relaxed\",\n)\nprint(combined.group_by(\"sheet\").len().sort(\"sheet\"))\n",[13,1106,1107,1117,1121,1143,1153,1175,1187,1191],{"__ignoreMap":141},[145,1108,1109,1111,1113,1115],{"class":147,"line":148},[145,1110,195],{"class":194},[145,1112,199],{"class":198},[145,1114,202],{"class":194},[145,1116,205],{"class":198},[145,1118,1119],{"class":147,"line":208},[145,1120,212],{"emptyLinePlaceholder":211},[145,1122,1123,1126,1128,1130,1133,1135,1137,1139,1141],{"class":147,"line":215},[145,1124,1125],{"class":198},"sheets ",[145,1127,221],{"class":194},[145,1129,224],{"class":198},[145,1131,1132],{"class":155},"\"year.xlsx\"",[145,1134,289],{"class":198},[145,1136,316],{"class":292},[145,1138,221],{"class":194},[145,1140,348],{"class":236},[145,1142,230],{"class":198},[145,1144,1145,1148,1150],{"class":147,"line":233},[145,1146,1147],{"class":198},"combined ",[145,1149,221],{"class":194},[145,1151,1152],{"class":198}," pl.concat(\n",[145,1154,1155,1158,1161,1163,1166,1168,1170,1172],{"class":147,"line":243},[145,1156,1157],{"class":198},"    [frame.with_columns(",[145,1159,1160],{"class":292},"sheet",[145,1162,221],{"class":194},[145,1164,1165],{"class":198},"pl.lit(name)) ",[145,1167,362],{"class":194},[145,1169,365],{"class":198},[145,1171,368],{"class":194},[145,1173,1174],{"class":198}," sheets.items()],\n",[145,1176,1177,1180,1182,1185],{"class":147,"line":251},[145,1178,1179],{"class":292},"    how",[145,1181,221],{"class":194},[145,1183,1184],{"class":155},"\"diagonal_relaxed\"",[145,1186,466],{"class":198},[145,1188,1189],{"class":147,"line":522},[145,1190,230],{"class":198},[145,1192,1193,1195,1198,1201,1204,1206],{"class":147,"line":528},[145,1194,237],{"class":236},[145,1196,1197],{"class":198},"(combined.group_by(",[145,1199,1200],{"class":155},"\"sheet\"",[145,1202,1203],{"class":198},").len().sort(",[145,1205,1200],{"class":155},[145,1207,1208],{"class":198},"))\n",[10,1210,1211,1214,1215,1218],{},[13,1212,1213],{},"diagonal_relaxed"," unions columns across sheets that do not agree, filling the gaps with nulls — the behaviour you want when a producer added a column halfway through the year. Use plain ",[13,1216,1217],{},"vertical"," instead when a schema change should stop the job rather than pass silently.",[131,1220,1222],{"id":1221},"common-pitfalls-and-gotchas","Common pitfalls and gotchas",[1224,1225,1226,1243,1249,1272,1293],"ul",{},[1227,1228,1229,1235,1236,1238,1239,1242],"li",{},[1230,1231,1232,1234],"strong",{},[13,1233,316],{}," is one-based."," Porting ",[13,1237,428],{}," from pandas to ",[13,1240,1241],{},"sheet_id=0"," reads nothing useful.",[1227,1244,1245,1248],{},[1230,1246,1247],{},"No index."," Anything that relied on a pandas index becomes an explicit column plus a join.",[1227,1250,1251,1260,1261,1264,1265,1267,1268,1271],{},[1230,1252,1253,1256,1257,26],{},[13,1254,1255],{},"null"," is not ",[13,1258,1259],{},"NaN"," ",[13,1262,1263],{},"fill_null(0)"," leaves ",[13,1266,1259],{}," values untouched; use ",[13,1269,1270],{},"fill_nan()"," for those.",[1227,1273,1274,1260,1277,1280,1281,289,1284,289,1287,664,1290,26],{},[1230,1275,1276],{},"Expressions do not evaluate on their own.",[13,1278,1279],{},"pl.col(\"x\") * 2"," is a plan; it computes inside ",[13,1282,1283],{},"select",[13,1285,1286],{},"with_columns",[13,1288,1289],{},"filter",[13,1291,1292],{},"agg",[1227,1294,1295,1300,1301,26],{},[1230,1296,1297,1299],{},[13,1298,846],{}," ignores formatting entirely"," — colours, merged cells, comments and formulas-as-written are invisible. Use openpyxl when those matter, as in ",[22,1302,1304],{"href":1303},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fread-cell-value-from-excel-with-openpyxl\u002F","Read cell value from Excel with openpyxl",[131,1306,1308],{"id":1307},"performance-and-scale-notes","Performance and scale notes",[10,1310,1311,1312,1316,1317,1321],{},"The parse is the expensive part of any Excel read, and calamine is several times faster than a pure-Python engine on the same file while using markedly less memory. Two habits compound that gain. Read only the columns you need — select immediately after the read so later operations carry less data — and convert workbooks you read repeatedly into Parquet, after which a lazy scan reads only the columns and row groups a query touches. Both are covered in ",[22,1313,1315],{"href":1314},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python\u002F","Convert Excel files to Parquet with Python",". For workbooks too large to hold at all, the chunking strategies in ",[22,1318,1320],{"href":1319},"\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"," still apply — Excel cannot be streamed, so the answer is always to convert once and stream the converted form.",[131,1323,1325],{"id":1324},"conclusion","Conclusion",[10,1327,1328,1331,1332,1334,1335,1337,1338,1340,1341,1343],{},[13,1329,1330],{},"pl.read_excel()"," gives you a fast, typed read in one call. Choose the sheet with ",[13,1333,293],{}," or the one-based ",[13,1336,316],{},", point the reader at the real header row with ",[13,1339,443],{},", and declare the columns that matter with ",[13,1342,553],{}," rather than repairing them later. From there the data is Arrow-backed and every transformation runs in parallel — and conversion to pandas remains available whenever the rest of your pipeline expects it.",[131,1345,1347],{"id":1346},"frequently-asked-questions","Frequently asked questions",[10,1349,1350,1353,1354,1356,1357,289,1360,289,1363,1366,1367,1370],{},[1230,1351,1352],{},"Which reader does polars.read_excel use?","\nBy default it uses calamine through the ",[13,1355,169],{}," package — a Rust parser that handles ",[13,1358,1359],{},".xlsx",[13,1361,1362],{},".xlsm",[13,1364,1365],{},".xls"," and ",[13,1368,1369],{},".ods",". That is why the install extra is needed and why the read is fast compared with a pure-Python engine.",[10,1372,1373,1376,1377,1380,1381,26],{},[1230,1374,1375],{},"How do I read every sheet at once?","\nPass ",[13,1378,1379],{},"sheet_id=None",". You get a dictionary keyed by sheet name, each value a DataFrame, the same shape pandas returns for ",[13,1382,1383],{},"sheet_name=None",[10,1385,1386,1389,1390,1393],{},[1230,1387,1388],{},"Why did my order numbers become floats?","\nType inference saw digits and chose a numeric type, which drops leading zeros. Pass ",[13,1391,1392],{},"schema_overrides={\"order_id\": pl.String}"," so the column is read as text from the start.",[10,1395,1396,1399,1400,1402,1403,1406],{},[1230,1397,1398],{},"Can Polars read a workbook from memory rather than a path?","\nYes. ",[13,1401,846],{}," accepts a file-like object, so an ",[13,1404,1405],{},"io.BytesIO"," holding a downloaded response works exactly like a path.",[10,1408,1409,1412],{},[1230,1410,1411],{},"Does read_excel see cell colours or comments?","\nNo. Polars reads values only. For formatting, comments, merged regions or anything else about presentation, use openpyxl.",[131,1414,1416],{"id":1415},"related","Related",[1224,1418,1419,1427,1434,1441,1448],{},[1227,1420,1421,1260,1424,1426],{},[1230,1422,1423],{},"Up:",[22,1425,25],{"href":24}," — where this reader fits among the Arrow-backed tools.",[1227,1428,1429,1433],{},[22,1430,1432],{"href":1431},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine\u002F","Speed up pandas Excel reads with the calamine engine"," — the same parser without changing DataFrame library.",[1227,1435,1436,1440],{},[22,1437,1439],{"href":1438},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fwrite-a-polars-dataframe-to-excel-with-formatting\u002F","Write a Polars DataFrame to Excel with formatting"," — the other half of the round trip.",[1227,1442,1443,1447],{},[22,1444,1446],{"href":1445},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fhow-to-read-excel-with-pandas-step-by-step\u002F","How to read Excel with pandas step by step"," — the pandas equivalent, argument for argument.",[1227,1449,1450,1454],{},[22,1451,1453],{"href":1452},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fread-specific-columns-from-excel-with-pandas\u002F","Read specific columns from Excel with pandas"," — narrowing a read, which matters more the larger the file gets.",[1456,1457,1458],"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":141,"searchDepth":208,"depth":208,"links":1460},[1461,1462,1463,1464,1465,1466,1467,1468,1469,1470,1471,1472,1473,1474],{"id":133,"depth":208,"text":134},{"id":181,"depth":208,"text":182},{"id":266,"depth":208,"text":267},{"id":436,"depth":208,"text":437},{"id":546,"depth":208,"text":547},{"id":840,"depth":208,"text":841},{"id":932,"depth":208,"text":933},{"id":977,"depth":208,"text":978},{"id":1097,"depth":208,"text":1098},{"id":1221,"depth":208,"text":1222},{"id":1307,"depth":208,"text":1308},{"id":1324,"depth":208,"text":1325},{"id":1346,"depth":208,"text":1347},{"id":1415,"depth":208,"text":1416},"2026-08-27","Read xlsx into Polars: pick sheets, set the header row, control column types with schema_overrides, handle dates and nulls, and convert to pandas when you need to.","md",[1479,1481,1483,1487,1489],{"q":1352,"a":1480},"By default it uses calamine through the fastexcel package — a Rust parser that handles xlsx, xlsm, xls and ods. That is why the install extra is needed and why the read is fast compared with a pure-Python engine.",{"q":1375,"a":1482},"Pass sheet_id=None. You get a dictionary keyed by sheet name, each value a DataFrame, the same shape pandas returns for sheet_name=None.",{"q":1388,"a":1484},{"Type inference saw digits and chose a numeric type, which drops leading zeros":1485},{" Pass schema_overrides={\"order_id\"":1486},"pl.String} so the column is read as text from the start.",{"q":1398,"a":1488},"Yes. read_excel accepts a file-like object, so an io.BytesIO holding a downloaded response works exactly like a path.",{"q":1411,"a":1490},"No. Polars reads values only. For formatting, comments, merged regions or anything else about presentation, use openpyxl.",{},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-an-excel-file-with-polars-read-excel",{"title":1494,"description":1495},"polars.read_excel: Read Excel in Polars","Use polars.read_excel end to end — sheet selection, header rows, schema_overrides, date parsing, null handling, reading from bytes, and converting to pandas.","read-an-excel-file-with-polars-read-excel","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-an-excel-file-with-polars-read-excel\u002Findex","how-to","7GmK53LRgyVTRbsZAstibtGqds0QLmDueXAbPe0NblY",[1501,1505],{"title":1502,"path":1503,"stem":1504,"children":-1},"Convert Excel Files to Parquet with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-excel-files-to-parquet-with-python\u002Findex",{"title":1506,"path":1507,"stem":1508,"children":-1},"Speed Up pandas Excel Reads with the calamine Engine","\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fspeed-up-pandas-excel-reads-with-the-calamine-engine\u002Findex",1788710154260]