[{"data":1,"prerenderedAt":2211},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars":2179,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars":2187,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars":2192,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars":2193},{"id":4,"title":5,"body":6,"dateModified":2146,"datePublished":2146,"description":2147,"extension":2148,"faq":2149,"keyword":2159,"kind":2160,"meta":2161,"navigation":111,"path":2169,"seo":2170,"slug":2173,"stem":2174,"topics":2175,"type":2177,"__hash__":2178},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars\u002Findex.md","Convert pandas Excel Code to Polars",{"type":7,"value":8,"toc":2133},"minimark",[9,24,29,66,76,80,83,542,560,564,567,930,979,1164,1168,1171,1335,1361,1365,1409,1498,1502,1508,1706,1720,1724,1727,1826,1846,1967,1971,2020,2024,2065,2069,2078,2084,2090,2096,2100,2129],[10,11,12,13,17,18,23],"p",{},"You have a pandas script that reads a sales export, cleans it, summarises it and writes an Excel report. It works, but it is getting slow, or you want to standardise on Polars. Rewriting it is not hard, but it is not a find-and-replace either: Polars has no index, distinguishes null from NaN, prefers expressions to ",[14,15,16],"code",{},"apply",", and names many operations differently. This page converts a realistic pandas report script line by line, explains each translation, and finishes with a test that runs both versions and proves they produce the same figures — the step that turns a rewrite into a safe migration. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002F","Reading Excel with Polars and Arrow",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas polars fastexcel xlsxwriter openpyxl\n","bash","",[14,37,38],{"__ignoreMap":35},[39,40,43,47,51,54,57,60,63],"span",{"class":41,"line":42},"line",1,[39,44,46],{"class":45},"sMTad","pip",[39,48,50],{"class":49},"srMev"," install",[39,52,53],{"class":49}," pandas",[39,55,56],{"class":49}," polars",[39,58,59],{"class":49}," fastexcel",[39,61,62],{"class":49}," xlsxwriter",[39,64,65],{"class":49}," openpyxl\n",[10,67,68,71,72,75],{},[14,69,70],{},"orders.xlsx"," holds 400 orders for the third quarter of 2026, with a region typed inconsistently and some blank discounts; ",[14,73,74],{},"regions.xlsx"," maps regions to managers. Both scripts below read these files and produce a monthly summary by region with the manager's name.",[25,77,79],{"id":78},"step-1-the-pandas-script-being-converted","Step 1 — The pandas script being converted",[10,81,82],{},"This is the kind of script most teams have — readable, and full of pandas idioms:",[30,84,88],{"className":85,"code":86,"language":87,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndef pandas_report(orders_path, regions_path):\n    df = pd.read_excel(orders_path)\n    regions = pd.read_excel(regions_path)\n    df[\"Region\"] = df[\"Region\"].str.strip().str.title()\n    df[\"Discount %\"] = df[\"Discount %\"].fillna(0)\n    df[\"Revenue\"] = df[\"Qty\"] * df[\"Unit price\"] * (1 - df[\"Discount %\"] \u002F 100)\n    df = df[df[\"Qty\"] > 0]\n    df[\"Month\"] = df[\"Date\"].dt.to_period(\"M\").astype(str)\n    summary = (df.groupby([\"Month\", \"Region\"], as_index=False)\n                 .agg(Orders=(\"Order ID\", \"count\"), Revenue=(\"Revenue\", \"sum\")))\n    summary = summary.merge(regions, on=\"Region\", how=\"left\")\n    summary[\"Revenue\"] = summary[\"Revenue\"].round(2)\n    wide = summary.pivot(index=\"Region\", columns=\"Month\", values=\"Revenue\").reset_index()\n    return summary.sort_values([\"Month\", \"Region\"]).reset_index(drop=True), wide\n\npd_summary, pd_wide = pandas_report(\"orders.xlsx\", \"regions.xlsx\")\nprint(pd_summary.head(4))\n","python",[14,89,90,106,113,126,138,149,171,197,251,274,305,337,379,408,433,472,501,506,527],{"__ignoreMap":35},[39,91,92,96,100,103],{"class":41,"line":42},[39,93,95],{"class":94},"s-kum","import",[39,97,99],{"class":98},"skGVy"," pandas ",[39,101,102],{"class":94},"as",[39,104,105],{"class":98}," pd\n",[39,107,109],{"class":41,"line":108},2,[39,110,112],{"emptyLinePlaceholder":111},true,"\n",[39,114,116,119,123],{"class":41,"line":115},3,[39,117,118],{"class":94},"def",[39,120,122],{"class":121},"s_Opv"," pandas_report",[39,124,125],{"class":98},"(orders_path, regions_path):\n",[39,127,129,132,135],{"class":41,"line":128},4,[39,130,131],{"class":98},"    df ",[39,133,134],{"class":94},"=",[39,136,137],{"class":98}," pd.read_excel(orders_path)\n",[39,139,141,144,146],{"class":41,"line":140},5,[39,142,143],{"class":98},"    regions ",[39,145,134],{"class":94},[39,147,148],{"class":98}," pd.read_excel(regions_path)\n",[39,150,152,155,158,161,163,166,168],{"class":41,"line":151},6,[39,153,154],{"class":98},"    df[",[39,156,157],{"class":49},"\"Region\"",[39,159,160],{"class":98},"] ",[39,162,134],{"class":94},[39,164,165],{"class":98}," df[",[39,167,157],{"class":49},[39,169,170],{"class":98},"].str.strip().str.title()\n",[39,172,174,176,179,181,183,185,187,190,194],{"class":41,"line":173},7,[39,175,154],{"class":98},[39,177,178],{"class":49},"\"Discount %\"",[39,180,160],{"class":98},[39,182,134],{"class":94},[39,184,165],{"class":98},[39,186,178],{"class":49},[39,188,189],{"class":98},"].fillna(",[39,191,193],{"class":192},"sP0c6","0",[39,195,196],{"class":98},")\n",[39,198,200,202,205,207,209,211,214,216,219,221,224,226,228,231,234,237,239,241,243,246,249],{"class":41,"line":199},8,[39,201,154],{"class":98},[39,203,204],{"class":49},"\"Revenue\"",[39,206,160],{"class":98},[39,208,134],{"class":94},[39,210,165],{"class":98},[39,212,213],{"class":49},"\"Qty\"",[39,215,160],{"class":98},[39,217,218],{"class":94},"*",[39,220,165],{"class":98},[39,222,223],{"class":49},"\"Unit price\"",[39,225,160],{"class":98},[39,227,218],{"class":94},[39,229,230],{"class":98}," (",[39,232,233],{"class":192},"1",[39,235,236],{"class":94}," -",[39,238,165],{"class":98},[39,240,178],{"class":49},[39,242,160],{"class":98},[39,244,245],{"class":94},"\u002F",[39,247,248],{"class":192}," 100",[39,250,196],{"class":98},[39,252,254,256,258,261,263,265,268,271],{"class":41,"line":253},9,[39,255,131],{"class":98},[39,257,134],{"class":94},[39,259,260],{"class":98}," df[df[",[39,262,213],{"class":49},[39,264,160],{"class":98},[39,266,267],{"class":94},">",[39,269,270],{"class":192}," 0",[39,272,273],{"class":98},"]\n",[39,275,277,279,282,284,286,288,291,294,297,300,303],{"class":41,"line":276},10,[39,278,154],{"class":98},[39,280,281],{"class":49},"\"Month\"",[39,283,160],{"class":98},[39,285,134],{"class":94},[39,287,165],{"class":98},[39,289,290],{"class":49},"\"Date\"",[39,292,293],{"class":98},"].dt.to_period(",[39,295,296],{"class":49},"\"M\"",[39,298,299],{"class":98},").astype(",[39,301,302],{"class":192},"str",[39,304,196],{"class":98},[39,306,308,311,313,316,318,321,323,326,330,332,335],{"class":41,"line":307},11,[39,309,310],{"class":98},"    summary ",[39,312,134],{"class":94},[39,314,315],{"class":98}," (df.groupby([",[39,317,281],{"class":49},[39,319,320],{"class":98},", ",[39,322,157],{"class":49},[39,324,325],{"class":98},"], ",[39,327,329],{"class":328},"sa561","as_index",[39,331,134],{"class":94},[39,333,334],{"class":192},"False",[39,336,196],{"class":98},[39,338,340,343,346,348,351,354,356,359,362,365,367,369,371,373,376],{"class":41,"line":339},12,[39,341,342],{"class":98},"                 .agg(",[39,344,345],{"class":328},"Orders",[39,347,134],{"class":94},[39,349,350],{"class":98},"(",[39,352,353],{"class":49},"\"Order ID\"",[39,355,320],{"class":98},[39,357,358],{"class":49},"\"count\"",[39,360,361],{"class":98},"), ",[39,363,364],{"class":328},"Revenue",[39,366,134],{"class":94},[39,368,350],{"class":98},[39,370,204],{"class":49},[39,372,320],{"class":98},[39,374,375],{"class":49},"\"sum\"",[39,377,378],{"class":98},")))\n",[39,380,382,384,386,389,392,394,396,398,401,403,406],{"class":41,"line":381},13,[39,383,310],{"class":98},[39,385,134],{"class":94},[39,387,388],{"class":98}," summary.merge(regions, ",[39,390,391],{"class":328},"on",[39,393,134],{"class":94},[39,395,157],{"class":49},[39,397,320],{"class":98},[39,399,400],{"class":328},"how",[39,402,134],{"class":94},[39,404,405],{"class":49},"\"left\"",[39,407,196],{"class":98},[39,409,411,414,416,418,420,423,425,428,431],{"class":41,"line":410},14,[39,412,413],{"class":98},"    summary[",[39,415,204],{"class":49},[39,417,160],{"class":98},[39,419,134],{"class":94},[39,421,422],{"class":98}," summary[",[39,424,204],{"class":49},[39,426,427],{"class":98},"].round(",[39,429,430],{"class":192},"2",[39,432,196],{"class":98},[39,434,436,439,441,444,447,449,451,453,456,458,460,462,465,467,469],{"class":41,"line":435},15,[39,437,438],{"class":98},"    wide ",[39,440,134],{"class":94},[39,442,443],{"class":98}," summary.pivot(",[39,445,446],{"class":328},"index",[39,448,134],{"class":94},[39,450,157],{"class":49},[39,452,320],{"class":98},[39,454,455],{"class":328},"columns",[39,457,134],{"class":94},[39,459,281],{"class":49},[39,461,320],{"class":98},[39,463,464],{"class":328},"values",[39,466,134],{"class":94},[39,468,204],{"class":49},[39,470,471],{"class":98},").reset_index()\n",[39,473,475,478,481,483,485,487,490,493,495,498],{"class":41,"line":474},16,[39,476,477],{"class":94},"    return",[39,479,480],{"class":98}," summary.sort_values([",[39,482,281],{"class":49},[39,484,320],{"class":98},[39,486,157],{"class":49},[39,488,489],{"class":98},"]).reset_index(",[39,491,492],{"class":328},"drop",[39,494,134],{"class":94},[39,496,497],{"class":192},"True",[39,499,500],{"class":98},"), wide\n",[39,502,504],{"class":41,"line":503},17,[39,505,112],{"emptyLinePlaceholder":111},[39,507,509,512,514,517,520,522,525],{"class":41,"line":508},18,[39,510,511],{"class":98},"pd_summary, pd_wide ",[39,513,134],{"class":94},[39,515,516],{"class":98}," pandas_report(",[39,518,519],{"class":49},"\"orders.xlsx\"",[39,521,320],{"class":98},[39,523,524],{"class":49},"\"regions.xlsx\"",[39,526,196],{"class":98},[39,528,530,533,536,539],{"class":41,"line":529},19,[39,531,532],{"class":192},"print",[39,534,535],{"class":98},"(pd_summary.head(",[39,537,538],{"class":192},"4",[39,540,541],{"class":98},"))\n",[10,543,544,545,548,549,320,552,555,556,559],{},"Assignments into ",[14,546,547],{},"df[...]",", boolean indexing, ",[14,550,551],{},"as_index=False",[14,553,554],{},"reset_index"," and ",[14,557,558],{},".dt.to_period"," are all things that change in Polars. The function returns both a long summary and a wide month-by-region table, as the report writes both.",[25,561,563],{"id":562},"step-2-the-same-script-in-polars","Step 2 — The same script in Polars",[10,565,566],{},"Here is the direct translation. Each pandas step has a Polars counterpart; the structure is a chain of expressions instead of a sequence of assignments:",[30,568,570],{"className":85,"code":569,"language":87,"meta":35,"style":35},"import polars as pl\n\ndef polars_report(orders_path, regions_path):\n    df = pl.read_excel(orders_path)\n    regions = pl.read_excel(regions_path)\n    df = (\n        df.with_columns(\n            pl.col(\"Region\").str.strip_chars().str.to_titlecase(),\n            pl.col(\"Discount %\").fill_null(0),\n        )\n        .with_columns(\n            (pl.col(\"Qty\") * pl.col(\"Unit price\") * (1 - pl.col(\"Discount %\") \u002F 100)).alias(\"Revenue\"),\n            pl.col(\"Date\").dt.strftime(\"%Y-%m\").alias(\"Month\"),\n        )\n        .filter(pl.col(\"Qty\") > 0)\n    )\n    summary = (\n        df.group_by(\"Month\", \"Region\")\n          .agg(pl.col(\"Order ID\").count().alias(\"Orders\"), pl.col(\"Revenue\").sum())\n          .join(regions, on=\"Region\", how=\"left\")\n          .with_columns(pl.col(\"Revenue\").round(2))\n          .sort(\"Month\", \"Region\")\n    )\n    wide = summary.pivot(on=\"Month\", index=\"Region\", values=\"Revenue\").sort(\"Region\")\n    return summary, wide\n\npl_summary, pl_wide = polars_report(\"orders.xlsx\", \"regions.xlsx\")\nprint(pl_summary.head(4))\n",[14,571,572,584,588,597,606,615,624,629,639,653,658,663,707,726,730,745,750,758,771,792,814,829,843,848,886,894,899,918],{"__ignoreMap":35},[39,573,574,576,579,581],{"class":41,"line":42},[39,575,95],{"class":94},[39,577,578],{"class":98}," polars ",[39,580,102],{"class":94},[39,582,583],{"class":98}," pl\n",[39,585,586],{"class":41,"line":108},[39,587,112],{"emptyLinePlaceholder":111},[39,589,590,592,595],{"class":41,"line":115},[39,591,118],{"class":94},[39,593,594],{"class":121}," polars_report",[39,596,125],{"class":98},[39,598,599,601,603],{"class":41,"line":128},[39,600,131],{"class":98},[39,602,134],{"class":94},[39,604,605],{"class":98}," pl.read_excel(orders_path)\n",[39,607,608,610,612],{"class":41,"line":140},[39,609,143],{"class":98},[39,611,134],{"class":94},[39,613,614],{"class":98}," pl.read_excel(regions_path)\n",[39,616,617,619,621],{"class":41,"line":151},[39,618,131],{"class":98},[39,620,134],{"class":94},[39,622,623],{"class":98}," (\n",[39,625,626],{"class":41,"line":173},[39,627,628],{"class":98},"        df.with_columns(\n",[39,630,631,634,636],{"class":41,"line":199},[39,632,633],{"class":98},"            pl.col(",[39,635,157],{"class":49},[39,637,638],{"class":98},").str.strip_chars().str.to_titlecase(),\n",[39,640,641,643,645,648,650],{"class":41,"line":253},[39,642,633],{"class":98},[39,644,178],{"class":49},[39,646,647],{"class":98},").fill_null(",[39,649,193],{"class":192},[39,651,652],{"class":98},"),\n",[39,654,655],{"class":41,"line":276},[39,656,657],{"class":98},"        )\n",[39,659,660],{"class":41,"line":307},[39,661,662],{"class":98},"        .with_columns(\n",[39,664,665,668,670,673,675,678,680,682,684,686,688,690,692,694,696,698,700,703,705],{"class":41,"line":339},[39,666,667],{"class":98},"            (pl.col(",[39,669,213],{"class":49},[39,671,672],{"class":98},") ",[39,674,218],{"class":94},[39,676,677],{"class":98}," pl.col(",[39,679,223],{"class":49},[39,681,672],{"class":98},[39,683,218],{"class":94},[39,685,230],{"class":98},[39,687,233],{"class":192},[39,689,236],{"class":94},[39,691,677],{"class":98},[39,693,178],{"class":49},[39,695,672],{"class":98},[39,697,245],{"class":94},[39,699,248],{"class":192},[39,701,702],{"class":98},")).alias(",[39,704,204],{"class":49},[39,706,652],{"class":98},[39,708,709,711,713,716,719,722,724],{"class":41,"line":381},[39,710,633],{"class":98},[39,712,290],{"class":49},[39,714,715],{"class":98},").dt.strftime(",[39,717,718],{"class":49},"\"%Y-%m\"",[39,720,721],{"class":98},").alias(",[39,723,281],{"class":49},[39,725,652],{"class":98},[39,727,728],{"class":41,"line":410},[39,729,657],{"class":98},[39,731,732,735,737,739,741,743],{"class":41,"line":435},[39,733,734],{"class":98},"        .filter(pl.col(",[39,736,213],{"class":49},[39,738,672],{"class":98},[39,740,267],{"class":94},[39,742,270],{"class":192},[39,744,196],{"class":98},[39,746,747],{"class":41,"line":474},[39,748,749],{"class":98},"    )\n",[39,751,752,754,756],{"class":41,"line":503},[39,753,310],{"class":98},[39,755,134],{"class":94},[39,757,623],{"class":98},[39,759,760,763,765,767,769],{"class":41,"line":508},[39,761,762],{"class":98},"        df.group_by(",[39,764,281],{"class":49},[39,766,320],{"class":98},[39,768,157],{"class":49},[39,770,196],{"class":98},[39,772,773,776,778,781,784,787,789],{"class":41,"line":529},[39,774,775],{"class":98},"          .agg(pl.col(",[39,777,353],{"class":49},[39,779,780],{"class":98},").count().alias(",[39,782,783],{"class":49},"\"Orders\"",[39,785,786],{"class":98},"), pl.col(",[39,788,204],{"class":49},[39,790,791],{"class":98},").sum())\n",[39,793,795,798,800,802,804,806,808,810,812],{"class":41,"line":794},20,[39,796,797],{"class":98},"          .join(regions, ",[39,799,391],{"class":328},[39,801,134],{"class":94},[39,803,157],{"class":49},[39,805,320],{"class":98},[39,807,400],{"class":328},[39,809,134],{"class":94},[39,811,405],{"class":49},[39,813,196],{"class":98},[39,815,817,820,822,825,827],{"class":41,"line":816},21,[39,818,819],{"class":98},"          .with_columns(pl.col(",[39,821,204],{"class":49},[39,823,824],{"class":98},").round(",[39,826,430],{"class":192},[39,828,541],{"class":98},[39,830,832,835,837,839,841],{"class":41,"line":831},22,[39,833,834],{"class":98},"          .sort(",[39,836,281],{"class":49},[39,838,320],{"class":98},[39,840,157],{"class":49},[39,842,196],{"class":98},[39,844,846],{"class":41,"line":845},23,[39,847,749],{"class":98},[39,849,851,853,855,857,859,861,863,865,867,869,871,873,875,877,879,882,884],{"class":41,"line":850},24,[39,852,438],{"class":98},[39,854,134],{"class":94},[39,856,443],{"class":98},[39,858,391],{"class":328},[39,860,134],{"class":94},[39,862,281],{"class":49},[39,864,320],{"class":98},[39,866,446],{"class":328},[39,868,134],{"class":94},[39,870,157],{"class":49},[39,872,320],{"class":98},[39,874,464],{"class":328},[39,876,134],{"class":94},[39,878,204],{"class":49},[39,880,881],{"class":98},").sort(",[39,883,157],{"class":49},[39,885,196],{"class":98},[39,887,889,891],{"class":41,"line":888},25,[39,890,477],{"class":94},[39,892,893],{"class":98}," summary, wide\n",[39,895,897],{"class":41,"line":896},26,[39,898,112],{"emptyLinePlaceholder":111},[39,900,902,905,907,910,912,914,916],{"class":41,"line":901},27,[39,903,904],{"class":98},"pl_summary, pl_wide ",[39,906,134],{"class":94},[39,908,909],{"class":98}," polars_report(",[39,911,519],{"class":49},[39,913,320],{"class":98},[39,915,524],{"class":49},[39,917,196],{"class":98},[39,919,921,923,926,928],{"class":41,"line":920},28,[39,922,532],{"class":192},[39,924,925],{"class":98},"(pl_summary.head(",[39,927,538],{"class":192},[39,929,541],{"class":98},[10,931,932,933,936,937,940,941,944,945,947,948,951,952,954,955,959,960,963,964,966,967,969,970,973,974,976,977,23],{},"The two versions line up step for step. Formatting the month with ",[14,934,935],{},"dt.strftime(\"%Y-%m\")"," replaces pandas' ",[14,938,939],{},"to_period(\"M\").astype(str)"," — Polars has no period type, and a string sorts and pivots exactly like the pandas version did. The result has the same twelve rows of month, region, orders, revenue and manager, and the table below maps the rest of the vocabulary. Two ",[14,942,943],{},"with_columns"," calls are needed because ",[14,946,364],{}," uses the filled ",[14,949,950],{},"Discount %",": expressions inside a single ",[14,953,943],{}," all see the ",[956,957,958],"em",{},"input"," frame, so a column created there is not visible to its siblings. ",[14,961,962],{},"group_by"," never sets an index, so there is no ",[14,965,329],{}," or ",[14,968,554],{},". ",[14,971,972],{},"pivot"," takes ",[14,975,391],{}," (the column whose values become headers) instead of ",[14,978,455],{},[980,981,989,990,989,994,989,998,989,1004,989,1012,989,1019,989,1026,989,1029,989,1033,989,1038,989,1044,989,1046,989,1051,989,1053,989,1056,989,1059,989,1063,989,1065,989,1069,989,1071,989,1074,989,1077,989,1081,989,1083,989,1087,989,1089,989,1092,989,1095,989,1099,989,1101,989,1105,989,1107,989,1110,989,1113,989,1117,989,1119,989,1123,989,1125,989,1128,989,1131,989,1135,989,1137,989,1141,989,1143,989,1146,989,1149,989,1153,989,1155,989,1159,989,1161],"svg",{"viewBox":982,"role":983,"ariaLabelledBy":984,"xmlns":987,"style":988},"4 0 752 310","img",[985,986],"pp-map-t","pp-map-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[991,992,993],"title",{"id":985},"pandas to Polars, the common translations",[995,996,997],"desc",{"id":986},"Assigning a column becomes with_columns with alias. Boolean indexing becomes filter. fillna becomes fill_null. groupby with agg becomes group_by with agg of expressions. merge becomes join. pivot with columns becomes pivot with on. to_excel becomes write_excel.",[999,1000],"rect",{"x":538,"y":193,"width":1001,"height":1002,"fill":1003},"752","310","#ffffff",[999,1005],{"x":1006,"y":1007,"width":1008,"height":1009,"rx":193,"fill":1003,"stroke":1010,"style":1011},"20.0","16.0","105.9","40.0","var(--line,#cdd5e6)","stroke-width:1px",[999,1013],{"x":1014,"y":1007,"width":1015,"height":1009,"rx":193,"fill":1016,"stroke":1017,"style":1018},"125.9","307.0","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[1020,1021,1025],"text",{"x":1022,"y":1023,"style":1024},"279.4","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","pandas",[999,1027],{"x":1028,"y":1007,"width":1015,"height":1009,"rx":193,"fill":1016,"stroke":1017,"style":1018},"433.0",[1020,1030,1032],{"x":1031,"y":1023,"style":1024},"586.5","Polars",[999,1034],{"x":1006,"y":1035,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"56.0","34.0","#e7ebef",[1020,1039,1043],{"x":1040,"y":1041,"style":1042},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","new column",[999,1045],{"x":1014,"y":1035,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1047,1050],{"x":1022,"y":1048,"style":1049},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","df['x'] = …",[999,1052],{"x":1028,"y":1035,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1054,1055],{"x":1031,"y":1048,"style":1049},"with_columns((…).alias('x'))",[999,1057],{"x":1006,"y":1058,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"90.0",[1020,1060,1062],{"x":1040,"y":1061,"style":1042},"112.0","filter rows",[999,1064],{"x":1014,"y":1058,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1066,1068],{"x":1022,"y":1067,"style":1049},"111.0","df[df['Qty'] > 0]",[999,1070],{"x":1028,"y":1058,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1072,1073],{"x":1031,"y":1067,"style":1049},"filter(pl.col('Qty') > 0)",[999,1075],{"x":1006,"y":1076,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"124.0",[1020,1078,1080],{"x":1040,"y":1079,"style":1042},"146.0","fill blanks",[999,1082],{"x":1014,"y":1076,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1084,1086],{"x":1022,"y":1085,"style":1049},"145.0","fillna(0)",[999,1088],{"x":1028,"y":1076,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1090,1091],{"x":1031,"y":1085,"style":1049},"fill_null(0)",[999,1093],{"x":1006,"y":1094,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"158.0",[1020,1096,1098],{"x":1040,"y":1097,"style":1042},"180.0","summarise",[999,1100],{"x":1014,"y":1094,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1102,1104],{"x":1022,"y":1103,"style":1049},"179.0","groupby(…).agg(…)",[999,1106],{"x":1028,"y":1094,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1108,1109],{"x":1031,"y":1103,"style":1049},"group_by(…).agg(exprs)",[999,1111],{"x":1006,"y":1112,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"192.0",[1020,1114,1116],{"x":1040,"y":1115,"style":1042},"214.0","lookup",[999,1118],{"x":1014,"y":1112,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1120,1122],{"x":1022,"y":1121,"style":1049},"213.0","merge(…)",[999,1124],{"x":1028,"y":1112,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1126,1127],{"x":1031,"y":1121,"style":1049},"join(…)",[999,1129],{"x":1006,"y":1130,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"226.0",[1020,1132,1134],{"x":1040,"y":1133,"style":1042},"248.0","reshape",[999,1136],{"x":1014,"y":1130,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1138,1140],{"x":1022,"y":1139,"style":1049},"247.0","pivot(columns=…)",[999,1142],{"x":1028,"y":1130,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1144,1145],{"x":1031,"y":1139,"style":1049},"pivot(on=…)",[999,1147],{"x":1006,"y":1148,"width":1008,"height":1036,"rx":193,"fill":1037,"stroke":1010,"style":1011},"260.0",[1020,1150,1152],{"x":1040,"y":1151,"style":1042},"282.0","write",[999,1154],{"x":1014,"y":1148,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1156,1158],{"x":1022,"y":1157,"style":1049},"281.0","to_excel(…)",[999,1160],{"x":1028,"y":1148,"width":1015,"height":1036,"rx":193,"fill":1003,"stroke":1010,"style":1011},[1020,1162,1163],{"x":1031,"y":1157,"style":1049},"write_excel(…)",[25,1165,1167],{"id":1166},"step-3-the-differences-that-bite","Step 3 — The differences that bite",[10,1169,1170],{},"A few behaviours differ enough to cause wrong results rather than errors. Check each against your own script:",[30,1172,1174],{"className":85,"code":1173,"language":87,"meta":35,"style":35},"import numpy as np\n\nprint(\"1. no index:\", type(pl_summary).__name__, \"has no .index; row order comes only from sort()\")\ns = pl.Series(\"x\", [1.0, None, float(\"nan\")])\nprint(\"2. null vs NaN:\", s.to_list(), \"| null_count:\", s.null_count(), \"| is_nan:\", s.is_nan().to_list())\nprint(\"3. fill_null does not touch NaN:\", s.fill_null(0).to_list(), \"| fill_nan:\", s.fill_nan(0).to_list())\nprint(\"4. pandas mean skips both:\", pd.Series([1.0, None, np.nan]).mean(), \"| polars mean with NaN:\", s.mean())\n",[14,1175,1176,1188,1192,1219,1256,1280,1308],{"__ignoreMap":35},[39,1177,1178,1180,1183,1185],{"class":41,"line":42},[39,1179,95],{"class":94},[39,1181,1182],{"class":98}," numpy ",[39,1184,102],{"class":94},[39,1186,1187],{"class":98}," np\n",[39,1189,1190],{"class":41,"line":108},[39,1191,112],{"emptyLinePlaceholder":111},[39,1193,1194,1196,1198,1201,1203,1206,1209,1212,1214,1217],{"class":41,"line":115},[39,1195,532],{"class":192},[39,1197,350],{"class":98},[39,1199,1200],{"class":49},"\"1. no index:\"",[39,1202,320],{"class":98},[39,1204,1205],{"class":192},"type",[39,1207,1208],{"class":98},"(pl_summary).",[39,1210,1211],{"class":192},"__name__",[39,1213,320],{"class":98},[39,1215,1216],{"class":49},"\"has no .index; row order comes only from sort()\"",[39,1218,196],{"class":98},[39,1220,1221,1224,1226,1229,1232,1235,1238,1240,1243,1245,1248,1250,1253],{"class":41,"line":128},[39,1222,1223],{"class":98},"s ",[39,1225,134],{"class":94},[39,1227,1228],{"class":98}," pl.Series(",[39,1230,1231],{"class":49},"\"x\"",[39,1233,1234],{"class":98},", [",[39,1236,1237],{"class":192},"1.0",[39,1239,320],{"class":98},[39,1241,1242],{"class":192},"None",[39,1244,320],{"class":98},[39,1246,1247],{"class":192},"float",[39,1249,350],{"class":98},[39,1251,1252],{"class":49},"\"nan\"",[39,1254,1255],{"class":98},")])\n",[39,1257,1258,1260,1262,1265,1268,1271,1274,1277],{"class":41,"line":140},[39,1259,532],{"class":192},[39,1261,350],{"class":98},[39,1263,1264],{"class":49},"\"2. null vs NaN:\"",[39,1266,1267],{"class":98},", s.to_list(), ",[39,1269,1270],{"class":49},"\"| null_count:\"",[39,1272,1273],{"class":98},", s.null_count(), ",[39,1275,1276],{"class":49},"\"| is_nan:\"",[39,1278,1279],{"class":98},", s.is_nan().to_list())\n",[39,1281,1282,1284,1286,1289,1292,1294,1297,1300,1303,1305],{"class":41,"line":151},[39,1283,532],{"class":192},[39,1285,350],{"class":98},[39,1287,1288],{"class":49},"\"3. fill_null does not touch NaN:\"",[39,1290,1291],{"class":98},", s.fill_null(",[39,1293,193],{"class":192},[39,1295,1296],{"class":98},").to_list(), ",[39,1298,1299],{"class":49},"\"| fill_nan:\"",[39,1301,1302],{"class":98},", s.fill_nan(",[39,1304,193],{"class":192},[39,1306,1307],{"class":98},").to_list())\n",[39,1309,1310,1312,1314,1317,1320,1322,1324,1326,1329,1332],{"class":41,"line":173},[39,1311,532],{"class":192},[39,1313,350],{"class":98},[39,1315,1316],{"class":49},"\"4. pandas mean skips both:\"",[39,1318,1319],{"class":98},", pd.Series([",[39,1321,1237],{"class":192},[39,1323,320],{"class":98},[39,1325,1242],{"class":192},[39,1327,1328],{"class":98},", np.nan]).mean(), ",[39,1330,1331],{"class":49},"\"| polars mean with NaN:\"",[39,1333,1334],{"class":98},", s.mean())\n",[10,1336,1337,1338,1341,1342,1345,1346,1349,1350,1353,1354,1357,1358,23],{},"First, there is no index: anything in the pandas script that relies on index alignment — assigning a Series computed from another frame, ",[14,1339,1340],{},"loc"," by label — must become an explicit join or a column. Second, Polars keeps ",[14,1343,1344],{},"null"," (missing) and ",[14,1347,1348],{},"NaN"," (a floating-point value) separate; reading Excel blanks gives nulls, but a division by zero produces NaN. Third, ",[14,1351,1352],{},"fill_null"," fills only nulls, so a column with NaN from a calculation needs ",[14,1355,1356],{},"fill_nan"," too. Fourth, aggregations treat NaN as a number: a NaN in a column makes its Polars mean NaN, where pandas would skip it. If your pandas code relied on NaN being skipped, convert NaN to null first with ",[14,1359,1360],{},"fill_nan(None)",[25,1362,1364],{"id":1363},"reading-and-writing-excel-the-engines-differ-too","Reading and writing Excel: the engines differ too",[10,1366,1367,1368,1371,1372,1375,1376,1379,1380,1383,1384,1387,1388,1391,1392,1395,1396,1400,1401,1404,1405,1408],{},"The two libraries do not just have different APIs; by default they read Excel with different engines, and that changes what arrives in the frame. pandas reads ",[14,1369,1370],{},".xlsx"," with openpyxl, Polars with calamine. Three differences matter in a migration. Dates: pandas gives ",[14,1373,1374],{},"datetime64"," timestamps, while Polars gives a ",[14,1377,1378],{},"Date"," column when the cells hold whole days and a ",[14,1381,1382],{},"Datetime"," when they hold times. Whole numbers: pandas returns ",[14,1385,1386],{},"int64"," only if a column has no blanks and ",[14,1389,1390],{},"float64"," otherwise, while Polars keeps integers with nulls as integers. Mismatched cells: pandas keeps a text value in a numeric column by making the column ",[14,1393,1394],{},"object",", while Polars' inference may turn it into a null without warning — see ",[19,1397,1399],{"href":1398},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Ffix-schema-and-type-errors-when-reading-excel-with-polars\u002F","Fix Schema and Type Errors When Reading Excel with Polars",". On the writing side, ",[14,1402,1403],{},"to_excel"," goes through openpyxl or XlsxWriter, while ",[14,1406,1407],{},"write_excel"," always uses XlsxWriter and formats the output as an Excel table by default, with autofilter buttons and banded rows. If the old report is post-processed with openpyxl or compared byte for byte, expect those differences and decide whether to keep them.",[980,1410,989,1415,989,1418,989,1421,989,1424,989,1430,989,1441,989,1446,989,1451,989,1455,989,1459,989,1463,989,1467,989,1470,989,1477,989,1482,989,1486,989,1489,989,1492,989,1495],{"viewBox":1411,"role":983,"ariaLabelledBy":1412,"xmlns":987,"style":988},"4 0 752 233",[1413,1414],"pp-engines-t","pp-engines-d",[991,1416,1417],{"id":1413},"What each library reads from the same workbook",[995,1419,1420],{"id":1414},"pandas reads with openpyxl and returns timestamps, floats for integer columns with blanks, and object columns where text and numbers mix. Polars reads with calamine and returns dates or datetimes, integers with nulls, and may turn cells that do not fit the inferred type into nulls.",[999,1422],{"x":538,"y":193,"width":1001,"height":1423,"fill":1003},"233",[999,1425],{"x":1006,"y":1007,"width":1426,"height":1427,"rx":1428,"fill":1037,"stroke":1010,"style":1429},"351.0","201.0","14","stroke-width:2px",[1431,1432,1434],"g",{"transform":1433},"translate(182.5,29.0) scale(1.083)",[1435,1436],"path",{"d":1437,"fill":1438,"stroke":1439,"style":1440},"M12 3c-4 0-4 1.6-4 3v2h4.2v1H6c-2 0-3 1.4-3 4s1 4 3 4h2v-2.6c0-1.6 1.3-2.9 2.9-2.9h4.2c1.3 0 2.4-1.1 2.4-2.4V6c0-1.6-1.8-3-5.5-3z M10 5.5h.01 M12 21c4 0 4-1.6 4-3v-2h-4.2v-1H18c2 0 3-1.4 3-4s-1-4-3-4h-2v2.6c0 1.6-1.3 2.9-2.9 2.9H8.9c-1.3 0-2.4 1.1-2.4 2.4V18c0 1.6 1.8 3 5.5 3z M14 18.5h.01","none","var(--text,#172033)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[1020,1442,1445],{"x":1443,"y":1041,"style":1444},"195.5","font-size:14px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","pandas (openpyxl)",[41,1447],{"x1":1448,"y1":1449,"x2":1450,"y2":1449,"stroke":1010,"style":1011},"36.0","91.0","355.0",[1020,1452,1454],{"x":1443,"y":1067,"style":1453},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","dates → datetime64",[1020,1456,1458],{"x":1443,"y":1457,"style":1453},"137.0","ints with blanks → float64",[1020,1460,1462],{"x":1443,"y":1461,"style":1453},"163.0","mixed column → object",[1020,1464,1466],{"x":1443,"y":1465,"style":1453},"189.0","to_excel: plain sheet",[999,1468],{"x":1469,"y":1007,"width":1426,"height":1427,"rx":1428,"fill":1016,"stroke":1017,"style":1429},"389.0",[1431,1471,1473],{"transform":1472},"translate(551.5,29.0) scale(1.083)",[1435,1474],{"d":1475,"fill":1438,"stroke":1476,"style":1440},"M13 2L4 14h7l-1 8 9-12h-7z","var(--brand-strong,#4338ca)",[1020,1478,1481],{"x":1479,"y":1041,"style":1480},"564.5","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Polars (calamine)",[41,1483],{"x1":1484,"y1":1449,"x2":1485,"y2":1449,"stroke":1017,"style":1011},"405.0","724.0",[1020,1487,1488],{"x":1479,"y":1067,"style":1453},"dates → Date or Datetime",[1020,1490,1491],{"x":1479,"y":1457,"style":1453},"ints with blanks stay Int64",[1020,1493,1494],{"x":1479,"y":1461,"style":1453},"misfit cells → null",[1020,1496,1497],{"x":1479,"y":1465,"style":1453},"write_excel: Excel table",[25,1499,1501],{"id":1500},"step-4-apply-becomes-an-expression","Step 4 — apply becomes an expression",[10,1503,1504,1505,1507],{},"pandas scripts often contain ",[14,1506,16],{}," with a Python function. In Polars, almost all of these become native expressions, which are much faster. A typical case — a size band from the quantity:",[30,1509,1511],{"className":85,"code":1510,"language":87,"meta":35,"style":35},"def band(q):\n    return \"large\" if q >= 10 else \"medium\" if q >= 5 else \"small\"\n\npd_orders = pd.read_excel(\"orders.xlsx\")\npd_orders[\"Band\"] = pd_orders[\"Qty\"].apply(band)\n\npl_orders = pl.read_excel(\"orders.xlsx\").with_columns(\n    pl.when(pl.col(\"Qty\") >= 10).then(pl.lit(\"large\"))\n      .when(pl.col(\"Qty\") >= 5).then(pl.lit(\"medium\"))\n      .otherwise(pl.lit(\"small\")).alias(\"Band\")\n)\nprint(\"same bands:\", pd_orders[\"Band\"].tolist() == pl_orders[\"Band\"].to_list())\n",[14,1512,1513,1523,1562,1566,1580,1600,1604,1619,1640,1660,1674,1678],{"__ignoreMap":35},[39,1514,1515,1517,1520],{"class":41,"line":42},[39,1516,118],{"class":94},[39,1518,1519],{"class":121}," band",[39,1521,1522],{"class":98},"(q):\n",[39,1524,1525,1527,1530,1533,1536,1539,1542,1545,1548,1550,1552,1554,1557,1559],{"class":41,"line":108},[39,1526,477],{"class":94},[39,1528,1529],{"class":49}," \"large\"",[39,1531,1532],{"class":94}," if",[39,1534,1535],{"class":98}," q ",[39,1537,1538],{"class":94},">=",[39,1540,1541],{"class":192}," 10",[39,1543,1544],{"class":94}," else",[39,1546,1547],{"class":49}," \"medium\"",[39,1549,1532],{"class":94},[39,1551,1535],{"class":98},[39,1553,1538],{"class":94},[39,1555,1556],{"class":192}," 5",[39,1558,1544],{"class":94},[39,1560,1561],{"class":49}," \"small\"\n",[39,1563,1564],{"class":41,"line":115},[39,1565,112],{"emptyLinePlaceholder":111},[39,1567,1568,1571,1573,1576,1578],{"class":41,"line":128},[39,1569,1570],{"class":98},"pd_orders ",[39,1572,134],{"class":94},[39,1574,1575],{"class":98}," pd.read_excel(",[39,1577,519],{"class":49},[39,1579,196],{"class":98},[39,1581,1582,1585,1588,1590,1592,1595,1597],{"class":41,"line":140},[39,1583,1584],{"class":98},"pd_orders[",[39,1586,1587],{"class":49},"\"Band\"",[39,1589,160],{"class":98},[39,1591,134],{"class":94},[39,1593,1594],{"class":98}," pd_orders[",[39,1596,213],{"class":49},[39,1598,1599],{"class":98},"].apply(band)\n",[39,1601,1602],{"class":41,"line":151},[39,1603,112],{"emptyLinePlaceholder":111},[39,1605,1606,1609,1611,1614,1616],{"class":41,"line":173},[39,1607,1608],{"class":98},"pl_orders ",[39,1610,134],{"class":94},[39,1612,1613],{"class":98}," pl.read_excel(",[39,1615,519],{"class":49},[39,1617,1618],{"class":98},").with_columns(\n",[39,1620,1621,1624,1626,1628,1630,1632,1635,1638],{"class":41,"line":199},[39,1622,1623],{"class":98},"    pl.when(pl.col(",[39,1625,213],{"class":49},[39,1627,672],{"class":98},[39,1629,1538],{"class":94},[39,1631,1541],{"class":192},[39,1633,1634],{"class":98},").then(pl.lit(",[39,1636,1637],{"class":49},"\"large\"",[39,1639,541],{"class":98},[39,1641,1642,1645,1647,1649,1651,1653,1655,1658],{"class":41,"line":253},[39,1643,1644],{"class":98},"      .when(pl.col(",[39,1646,213],{"class":49},[39,1648,672],{"class":98},[39,1650,1538],{"class":94},[39,1652,1556],{"class":192},[39,1654,1634],{"class":98},[39,1656,1657],{"class":49},"\"medium\"",[39,1659,541],{"class":98},[39,1661,1662,1665,1668,1670,1672],{"class":41,"line":276},[39,1663,1664],{"class":98},"      .otherwise(pl.lit(",[39,1666,1667],{"class":49},"\"small\"",[39,1669,702],{"class":98},[39,1671,1587],{"class":49},[39,1673,196],{"class":98},[39,1675,1676],{"class":41,"line":307},[39,1677,196],{"class":98},[39,1679,1680,1682,1684,1687,1690,1692,1695,1698,1701,1703],{"class":41,"line":339},[39,1681,532],{"class":192},[39,1683,350],{"class":98},[39,1685,1686],{"class":49},"\"same bands:\"",[39,1688,1689],{"class":98},", pd_orders[",[39,1691,1587],{"class":49},[39,1693,1694],{"class":98},"].tolist() ",[39,1696,1697],{"class":94},"==",[39,1699,1700],{"class":98}," pl_orders[",[39,1702,1587],{"class":49},[39,1704,1705],{"class":98},"].to_list())\n",[10,1707,1708,1711,1712,1715,1716,23],{},[14,1709,1710],{},"pl.when().then().otherwise()"," chains replace if\u002Felif\u002Felse logic; string methods, date arithmetic and lookups have expression forms too. When a function truly cannot be expressed — calling an external library on each value, say — ",[14,1713,1714],{},"map_elements"," is the escape hatch, but it runs Python per row and gives up most of the speed, so treat it as the exception. Conditional columns are compared in depth in ",[19,1717,1719],{"href":1718},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-if-formulas-as-pandas-conditional-columns\u002F","Excel IF Formulas as pandas Conditional Columns",[25,1721,1723],{"id":1722},"step-5-prove-both-versions-agree","Step 5 — Prove both versions agree",[10,1725,1726],{},"The migration is finished only when the new script produces the same numbers as the old one. Convert the Polars results to pandas and compare with a tolerance:",[30,1728,1730],{"className":85,"code":1729,"language":87,"meta":35,"style":35},"from pandas.testing import assert_frame_equal\n\nassert_frame_equal(pd_summary, pl_summary.to_pandas(), check_dtype=False)\npw = pd_wide.set_index(\"Region\").sort_index()\nqw = pl_wide.to_pandas().set_index(\"Region\").sort_index()\nassert_frame_equal(pw, qw[pw.columns], check_dtype=False, check_names=False)\nprint(\"summary and wide tables identical:\", pd_summary.shape, pd_wide.shape)\n",[14,1731,1732,1745,1749,1763,1778,1792,1814],{"__ignoreMap":35},[39,1733,1734,1737,1740,1742],{"class":41,"line":42},[39,1735,1736],{"class":94},"from",[39,1738,1739],{"class":98}," pandas.testing ",[39,1741,95],{"class":94},[39,1743,1744],{"class":98}," assert_frame_equal\n",[39,1746,1747],{"class":41,"line":108},[39,1748,112],{"emptyLinePlaceholder":111},[39,1750,1751,1754,1757,1759,1761],{"class":41,"line":115},[39,1752,1753],{"class":98},"assert_frame_equal(pd_summary, pl_summary.to_pandas(), ",[39,1755,1756],{"class":328},"check_dtype",[39,1758,134],{"class":94},[39,1760,334],{"class":192},[39,1762,196],{"class":98},[39,1764,1765,1768,1770,1773,1775],{"class":41,"line":128},[39,1766,1767],{"class":98},"pw ",[39,1769,134],{"class":94},[39,1771,1772],{"class":98}," pd_wide.set_index(",[39,1774,157],{"class":49},[39,1776,1777],{"class":98},").sort_index()\n",[39,1779,1780,1783,1785,1788,1790],{"class":41,"line":140},[39,1781,1782],{"class":98},"qw ",[39,1784,134],{"class":94},[39,1786,1787],{"class":98}," pl_wide.to_pandas().set_index(",[39,1789,157],{"class":49},[39,1791,1777],{"class":98},[39,1793,1794,1797,1799,1801,1803,1805,1808,1810,1812],{"class":41,"line":151},[39,1795,1796],{"class":98},"assert_frame_equal(pw, qw[pw.columns], ",[39,1798,1756],{"class":328},[39,1800,134],{"class":94},[39,1802,334],{"class":192},[39,1804,320],{"class":98},[39,1806,1807],{"class":328},"check_names",[39,1809,134],{"class":94},[39,1811,334],{"class":192},[39,1813,196],{"class":98},[39,1815,1816,1818,1820,1823],{"class":41,"line":173},[39,1817,532],{"class":192},[39,1819,350],{"class":98},[39,1821,1822],{"class":49},"\"summary and wide tables identical:\"",[39,1824,1825],{"class":98},", pd_summary.shape, pd_wide.shape)\n",[10,1827,1828,1831,1832,1835,1836,1838,1839,1841,1842,23],{},[14,1829,1830],{},"assert_frame_equal"," compares values column by column with a small tolerance for floats; ",[14,1833,1834],{},"check_dtype=False"," allows harmless differences such as Polars' unsigned integer counts versus pandas' int64. Keep this test in the project while both versions exist, run it on several real input files rather than one, and remove the pandas version only when it passes on all of them. Writing the final workbook is then ",[14,1837,1407],{}," instead of ",[14,1840,1403],{}," — see ",[19,1843,1845],{"href":1844},"\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",[980,1847,989,1852,989,1855,989,1858,989,1861,989,1866,989,1872,989,1878,989,1882,989,1887,989,1892,989,1895,989,1901,989,1907,989,1911,989,1915,989,1918,989,1921,989,1926,989,1933,989,1938,989,1941,989,1945,989,1948,989,1953,989,1960,989,1965],{"viewBox":1848,"role":983,"ariaLabelledBy":1849,"xmlns":987,"style":988},"4 0 752 142",[1850,1851],"pp-migrate-t","pp-migrate-d",[991,1853,1854],{"id":1850},"A safe migration from pandas to Polars",[995,1856,1857],{"id":1851},"Keep the pandas script working, translate it step by step into Polars, check the four behaviour differences, replace apply calls with expressions, and compare both outputs on several real files before retiring the pandas version.",[999,1859],{"x":538,"y":193,"width":1001,"height":1860,"fill":1003},"142",[999,1862],{"x":1006,"y":1007,"width":1863,"height":1864,"rx":1865,"fill":1037,"stroke":1010,"style":1429},"153.0","110.5","12",[1431,1867,1869],{"transform":1868},"translate(84.5,37.8) scale(1.000)",[1435,1870],{"d":1437,"fill":1438,"stroke":1439,"style":1871},"stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1020,1873,1877],{"x":1874,"y":1875,"style":1876},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Keep pandas",[1020,1879,1881],{"x":1874,"y":1880,"style":1049},"103.8","the reference result",[41,1883],{"x1":1884,"y1":1885,"x2":1886,"y2":1885,"stroke":1017,"style":1429},"178.0","71.2","200.0",[1888,1889],"polygon",{"points":1890,"fill":1891},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[999,1893],{"x":1894,"y":1007,"width":1863,"height":1864,"rx":1865,"fill":1016,"stroke":1017,"style":1429},"209.0",[1431,1896,1898],{"transform":1897},"translate(273.5,29.0) scale(1.000)",[1435,1899],{"d":1900,"fill":1438,"stroke":1476,"style":1871},"M9 8l-4 4 4 4 M15 8l4 4-4 4",[1020,1902,1906],{"x":1903,"y":1904,"style":1905},"285.5","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Translate",[1020,1908,1910],{"x":1903,"y":1909,"style":1049},"95.0","with_columns, filter,",[1020,1912,1914],{"x":1903,"y":1913,"style":1049},"112.5","join",[41,1916],{"x1":1917,"y1":1885,"x2":1469,"y2":1885,"stroke":1017,"style":1429},"367.0",[1888,1919],{"points":1920,"fill":1891},"393.0,71.2 384.0,76.2 384.0,66.2",[999,1922],{"x":1923,"y":1007,"width":1863,"height":1864,"rx":1865,"fill":1924,"stroke":1925,"style":1429},"398.0","#fdefd8","var(--gold,#b4740a)",[1431,1927,1929],{"transform":1928},"translate(462.5,37.8) scale(1.000)",[1435,1930],{"d":1931,"fill":1438,"stroke":1932,"style":1871},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[1020,1934,1937],{"x":1935,"y":1875,"style":1936},"474.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Check differences",[1020,1939,1940],{"x":1935,"y":1880,"style":1049},"index, null vs NaN",[41,1942],{"x1":1943,"y1":1885,"x2":1944,"y2":1885,"stroke":1017,"style":1429},"556.0","578.0",[1888,1946],{"points":1947,"fill":1891},"582.0,71.2 573.0,76.2 573.0,66.2",[999,1949],{"x":1950,"y":1007,"width":1863,"height":1864,"rx":1865,"fill":1951,"stroke":1952,"style":1429},"587.0","#d9f4f1","var(--teal,#0f9488)",[1431,1954,1956],{"transform":1955},"translate(651.5,37.8) scale(1.000)",[1435,1957],{"d":1958,"fill":1438,"stroke":1959,"style":1871},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[1020,1961,1964],{"x":1962,"y":1875,"style":1963},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Compare outputs",[1020,1966,1830],{"x":1962,"y":1880,"style":1049},[25,1968,1970],{"id":1969},"common-pitfalls","Common pitfalls",[1972,1973,1974,1985,1991,2003,2014],"ul",{},[1975,1976,1977,1984],"li",{},[1978,1979,1980,1981,1983],"strong",{},"Using a column in the same ",[14,1982,943],{}," that creates it."," Split into two calls.",[1975,1986,1987,1990],{},[1978,1988,1989],{},"Relying on index alignment."," Polars has no index; join explicitly.",[1975,1992,1993,1996,1997,1999,2000,2002],{},[1978,1994,1995],{},"NaN from calculations."," ",[14,1998,1352],{}," ignores it; use ",[14,2001,1356],{}," or convert NaN to null.",[1975,2004,2005,2010,2011,2013],{},[1978,2006,2007,2008,23],{},"Keeping ",[14,2009,16],{}," Rewrite as expressions; use ",[14,2012,1714],{}," only as a last resort.",[1975,2015,2016,2019],{},[1978,2017,2018],{},"Migrating without a comparison test."," Prove the outputs match on real files.",[25,2021,2023],{"id":2022},"conclusion","Conclusion",[10,2025,2026,2027,2029,2030,2033,2034,2036,2037,320,2040,2043,2044,2036,2046,320,2049,555,2052,2054,2055,2057,2058,2060,2061,2064],{},"Converting pandas Excel code to Polars is a translation of vocabulary — ",[14,2028,943],{}," for new columns, ",[14,2031,2032],{},"filter"," for boolean indexing, ",[14,2035,1352],{}," for ",[14,2038,2039],{},"fillna",[14,2041,2042],{},"group_by().agg()"," with expressions, ",[14,2045,1914],{},[14,2047,2048],{},"merge",[14,2050,2051],{},"pivot(on=)",[14,2053,1407],{}," — plus four behavioural differences to check: no index, null versus NaN, ",[14,2056,1352],{}," leaving NaN, and NaN propagating through aggregations. Replace ",[14,2059,16],{}," with ",[14,2062,2063],{},"when\u002Fthen\u002Fotherwise"," and other expressions, and keep a test that compares both versions' output until the pandas script can safely be retired.",[25,2066,2068],{"id":2067},"frequently-asked-questions","Frequently asked questions",[10,2070,2071,2077],{},[1978,2072,2073,2074,2076],{},"How do I add a column in Polars like df",[39,2075,1231],{}," = … in pandas?","\nUse with_columns with an expression and alias, for example df.with_columns((pl.col(\"a\") * 2).alias(\"x\")). Polars returns a new DataFrame rather than modifying it in place.",[10,2079,2080,2083],{},[1978,2081,2082],{},"What replaces pandas apply in Polars?","\nNative expressions such as pl.when().then().otherwise(), string and date methods. map_elements exists for functions that cannot be expressed but is much slower.",[10,2085,2086,2089],{},[1978,2087,2088],{},"Why does my Polars mean return NaN when pandas did not?","\nPolars treats NaN as a number, so one NaN makes the mean NaN, while pandas skips it. Convert NaN to null with fill_nan(None) first.",[10,2091,2092,2095],{},[1978,2093,2094],{},"How do I check a Polars rewrite gives the same results?","\nRun both versions on the same input, convert the Polars result with to_pandas, and compare them with assert_frame_equal from the pandas testing module.",[25,2097,2099],{"id":2098},"related","Related",[1972,2101,2102,2108,2115,2122],{},[1975,2103,2104,2105,2107],{},"Up one level: ",[19,2106,22],{"href":21}," — fast Excel reading with Polars.",[1975,2109,2110,2114],{},[19,2111,2113],{"href":2112},"\u002Fgetting-started-with-python-excel-automation\u002Fchoosing-a-python-excel-library\u002Fpandas-vs-polars-for-excel-workflows\u002F","pandas vs Polars for Excel Workflows"," — whether to switch at all.",[1975,2116,2117,2121],{},[19,2118,2120],{"href":2119},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fuse-polars-window-functions-on-excel-data\u002F","Use Polars Window Functions on Excel Data"," — the Polars form of groupby transform.",[1975,2123,2124,2128],{},[19,2125,2127],{"href":2126},"\u002Fautomating-reporting-workflows\u002Ftesting-and-packaging-excel-automation-scripts\u002Fcompare-excel-output-to-a-golden-file-in-tests\u002F","Compare Excel Output to a Golden File in Tests"," — testing the written workbook too.",[2130,2131,2132],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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}",{"title":35,"searchDepth":108,"depth":108,"links":2134},[2135,2136,2137,2138,2139,2140,2141,2142,2143,2144,2145],{"id":27,"depth":108,"text":28},{"id":78,"depth":108,"text":79},{"id":562,"depth":108,"text":563},{"id":1166,"depth":108,"text":1167},{"id":1363,"depth":108,"text":1364},{"id":1500,"depth":108,"text":1501},{"id":1722,"depth":108,"text":1723},{"id":1969,"depth":108,"text":1970},{"id":2022,"depth":108,"text":2023},{"id":2067,"depth":108,"text":2068},{"id":2098,"depth":108,"text":2099},"2026-10-02","Port an existing pandas Excel report script to Polars step by step: read_excel and to_excel equivalents, filtering, new columns, groupby and merge, pivot and fillna, the index that Polars does not have, NaN versus null, apply calls that become expressions, and a test that proves the new script produces the same workbook figures as the old one.","md",[2150,2153,2155,2157],{"q":2151,"a":2152},"How do I add a column in Polars like df[\"x\"] = … in pandas?","Use with_columns with an expression and alias, for example df.with_columns((pl.col(\"a\") * 2).alias(\"x\")). Polars returns a new DataFrame rather than modifying it in place.",{"q":2082,"a":2154},"Native expressions such as pl.when().then().otherwise(), string and date methods. map_elements exists for functions that cannot be expressed but is much slower.",{"q":2088,"a":2156},"Polars treats NaN as a number, so one NaN makes the mean NaN, while pandas skips it. Convert NaN to null with fill_nan(None) first.",{"q":2094,"a":2158},"Run both versions on the same input, convert the Polars result with to_pandas, and compare them with assert_frame_equal from the pandas testing module.","convert pandas excel code to polars",null,{"breadcrumb":2162},[2163,2165,2168],{"name":2164,"item":245},"Home",{"name":2166,"item":2167},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":22,"item":21},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars",{"title":2171,"description":2172},"Convert a pandas Excel Script to Polars Step by Step","Translate pandas Excel code to Polars: read_excel, filter, with_columns, group_by, join, pivot, fill_null, no index, NaN vs null, apply to expressions, write_excel, and a test comparing old and new output.","convert-pandas-excel-code-to-polars","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars\u002Findex",[2176,1025],"polars","how-to","WPiAvMCdn4RZxD1ibUhkw7_3YDIDR0C-i281ugJyjDM",[2180,2184],{"title":2181,"path":2182,"stem":2183,"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":1399,"path":2185,"stem":2186,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Ffix-schema-and-type-errors-when-reading-excel-with-polars","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Ffix-schema-and-type-errors-when-reading-excel-with-polars\u002Findex",[2188,2190],{"path":2189,"title":2166},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2191,"title":22},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow",[],[2194,2197,2200,2202,2205,2208],{"path":2195,"title":2196,"kind":2177},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-and-aggregate-excel-data-with-polars","Clean and Aggregate Excel Data with Polars Expressions",{"path":2198,"title":2199,"kind":2177},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors","Clean Wide Excel Sheets with Polars Column Selectors",{"path":2185,"title":1399,"kind":2201},"fix",{"path":2203,"title":2204,"kind":2177},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fjoin-and-pivot-excel-data-with-polars","Join and Pivot Excel Data with Polars",{"path":2206,"title":2207,"kind":2177},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-an-excel-file-with-polars-read-excel","Read an Excel File with polars.read_excel",{"path":2209,"title":2210,"kind":2177},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-multiple-excel-sheets-with-polars","Read Multiple Excel Sheets with Polars",1790972098190]