[{"data":1,"prerenderedAt":2094},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors":2062,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors":2071,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors":2076,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors":2077},{"id":4,"title":5,"body":6,"dateModified":2030,"datePublished":2030,"description":2031,"extension":2032,"faq":2033,"keyword":2042,"kind":2043,"meta":2044,"navigation":120,"path":2052,"seo":2053,"slug":2055,"stem":2056,"topics":2057,"type":2060,"__hash__":2061},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors\u002Findex.md","Clean Wide Excel Sheets with Polars Column Selectors",{"type":7,"value":8,"toc":2017},"minimark",[9,29,34,62,68,72,79,275,300,431,435,442,540,572,576,583,836,879,965,969,976,1156,1166,1170,1173,1384,1398,1611,1615,1633,1839,1859,1863,1905,1909,1952,1956,1962,1968,1974,1980,1984,2013],[10,11,12,13,17,18,22,23,28],"p",{},"Finance and operations sheets are often wide: a product per row and a column for every month, often twice over — \"Sales Jan\" to \"Sales Dec\", then \"Returns Jan\" to \"Returns Dec\" — plus a handful of descriptive columns. Cleaning such a sheet in code by listing columns by name is long, fragile and breaks the month a new column appears. Polars has a better tool: ",[14,15,16],"em",{},"column selectors",", in the ",[19,20,21],"code",{},"polars.selectors"," module. A selector describes a set of columns — all numeric ones, all whose names start with \"Sales \", all matching a regular expression — and selectors combine like sets. One expression then applies to every column in the set. This page cleans a wide sales sheet with selectors: trimming text, filling numeric gaps, computing totals across a block, renaming, and unpivoting into a long table. It belongs to ",[24,25,27],"a",{"href":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002F","Reading Excel with Polars and Arrow",".",[30,31,33],"h2",{"id":32},"prerequisites","Prerequisites",[35,36,41],"pre",{"className":37,"code":38,"language":39,"meta":40,"style":40},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install polars fastexcel\n","bash","",[19,42,43],{"__ignoreMap":40},[44,45,48,52,56,59],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," polars",[44,60,61],{"class":54}," fastexcel\n",[10,63,64,67],{},[19,65,66],{},"wide_sales.xlsx"," has six products and 22 columns: SKU, product name and category (with stray spaces and inconsistent capitals), nine monthly sales columns, nine monthly returns columns with a few blanks, and a notes column. The examples were run with Polars 1.44.",[30,69,71],{"id":70},"step-1-read-the-sheet-and-see-the-selectors-at-work","Step 1 — Read the sheet and see the selectors at work",[10,73,74,75,78],{},"Import the selectors module as ",[19,76,77],{},"cs"," — the convention in Polars' own documentation — and ask which columns a few selectors match:",[35,80,84],{"className":81,"code":82,"language":83,"meta":40,"style":40},"language-python shiki shiki-themes github-light github-dark-high-contrast","import polars as pl\nimport polars.selectors as cs\n\ndf = pl.read_excel(\"wide_sales.xlsx\")\nprint(df.shape)\nfor name, sel in {\n    \"numeric\": cs.numeric(),\n    \"string\": cs.string(),\n    \"sales block\": cs.starts_with(\"Sales \"),\n    \"Q2 months\": cs.matches(r\" (Apr|May|Jun)$\"),\n}.items():\n    print(f\"{name:12}\", cs.expand_selector(df, sel))\n","python",[19,85,86,102,115,122,140,150,165,174,183,198,238,244],{"__ignoreMap":40},[44,87,88,92,96,99],{"class":46,"line":47},[44,89,91],{"class":90},"s-kum","import",[44,93,95],{"class":94},"skGVy"," polars ",[44,97,98],{"class":90},"as",[44,100,101],{"class":94}," pl\n",[44,103,105,107,110,112],{"class":46,"line":104},2,[44,106,91],{"class":90},[44,108,109],{"class":94}," polars.selectors ",[44,111,98],{"class":90},[44,113,114],{"class":94}," cs\n",[44,116,118],{"class":46,"line":117},3,[44,119,121],{"emptyLinePlaceholder":120},true,"\n",[44,123,125,128,131,134,137],{"class":46,"line":124},4,[44,126,127],{"class":94},"df ",[44,129,130],{"class":90},"=",[44,132,133],{"class":94}," pl.read_excel(",[44,135,136],{"class":54},"\"wide_sales.xlsx\"",[44,138,139],{"class":94},")\n",[44,141,143,147],{"class":46,"line":142},5,[44,144,146],{"class":145},"sP0c6","print",[44,148,149],{"class":94},"(df.shape)\n",[44,151,153,156,159,162],{"class":46,"line":152},6,[44,154,155],{"class":90},"for",[44,157,158],{"class":94}," name, sel ",[44,160,161],{"class":90},"in",[44,163,164],{"class":94}," {\n",[44,166,168,171],{"class":46,"line":167},7,[44,169,170],{"class":54},"    \"numeric\"",[44,172,173],{"class":94},": cs.numeric(),\n",[44,175,177,180],{"class":46,"line":176},8,[44,178,179],{"class":54},"    \"string\"",[44,181,182],{"class":94},": cs.string(),\n",[44,184,186,189,192,195],{"class":46,"line":185},9,[44,187,188],{"class":54},"    \"sales block\"",[44,190,191],{"class":94},": cs.starts_with(",[44,193,194],{"class":54},"\"Sales \"",[44,196,197],{"class":94},"),\n",[44,199,201,204,207,210,213,216,219,222,225,227,230,233,236],{"class":46,"line":200},10,[44,202,203],{"class":54},"    \"Q2 months\"",[44,205,206],{"class":94},": cs.matches(",[44,208,209],{"class":90},"r",[44,211,212],{"class":54},"\" ",[44,214,215],{"class":145},"(",[44,217,218],{"class":54},"Apr",[44,220,221],{"class":90},"|",[44,223,224],{"class":54},"May",[44,226,221],{"class":90},[44,228,229],{"class":54},"Jun",[44,231,232],{"class":145},")$",[44,234,235],{"class":54},"\"",[44,237,197],{"class":94},[44,239,241],{"class":46,"line":240},11,[44,242,243],{"class":94},"}.items():\n",[44,245,247,250,252,255,257,261,264,267,270,272],{"class":46,"line":246},12,[44,248,249],{"class":145},"    print",[44,251,215],{"class":94},[44,253,254],{"class":90},"f",[44,256,235],{"class":54},[44,258,260],{"class":259},"sSjpA","{",[44,262,263],{"class":94},"name",[44,265,266],{"class":90},":12",[44,268,269],{"class":259},"}",[44,271,235],{"class":54},[44,273,274],{"class":94},", cs.expand_selector(df, sel))\n",[10,276,277,280,281,284,285,288,289,292,293,296,297,299],{},[19,278,279],{},"cs.expand_selector"," shows exactly which columns a selector resolves to — useful while writing one. ",[19,282,283],{},"cs.numeric()"," picks every numeric column, ",[19,286,287],{},"cs.string()"," every text column, ",[19,290,291],{},"cs.starts_with(\"Sales \")"," the nine sales months, and ",[19,294,295],{},"cs.matches()"," takes a regular expression, here selecting April to June across both blocks. Selectors are evaluated against the frame's actual columns at run time, so a \"Sales Oct\" column added next month is picked up automatically by ",[19,298,291],{}," without any code change.",[301,302,310,311,310,315,310,319,310,327,310,335,310,342,310,349,310,352,310,356,310,361,310,367,310,369,310,373,310,375,310,378,310,381,310,385,310,387,310,390,310,392,310,395,310,398,310,402,310,404,310,408,310,410,310,413,310,416,310,420,310,422,310,426,310,428],"svg",{"viewBox":303,"role":304,"ariaLabelledBy":305,"xmlns":308,"style":309},"4 0 752 208","img",[306,307],"sel-kinds-t","sel-kinds-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  ",[312,313,314],"title",{"id":306},"Selectors and what they pick in this sheet",[316,317,318],"desc",{"id":307},"cs.string picks SKU, Product, Category and Notes. cs.numeric picks the eighteen monthly columns. cs.starts_with Sales picks the nine sales months, cs.starts_with Returns the nine returns months, and cs.matches with a regular expression picks April to June in both blocks.",[320,321],"rect",{"x":322,"y":323,"width":324,"height":325,"fill":326},"4","0","752","208","#ffffff",[320,328],{"x":329,"y":330,"width":331,"height":332,"rx":323,"fill":326,"stroke":333,"style":334},"20.0","16.0","125.9","40.0","var(--line,#cdd5e6)","stroke-width:1px",[320,336],{"x":337,"y":330,"width":338,"height":332,"rx":323,"fill":339,"stroke":340,"style":341},"145.9","297.1","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[343,344,348],"text",{"x":345,"y":346,"style":347},"294.4","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","selector",[320,350],{"x":351,"y":330,"width":338,"height":332,"rx":323,"fill":339,"stroke":340,"style":341},"442.9",[343,353,355],{"x":354,"y":346,"style":347},"591.5","columns picked",[320,357],{"x":329,"y":358,"width":331,"height":359,"rx":323,"fill":360,"stroke":333,"style":334},"56.0","34.0","#e7ebef",[343,362,366],{"x":363,"y":364,"style":365},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","text columns",[320,368],{"x":337,"y":358,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,370,287],{"x":345,"y":371,"style":372},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[320,374],{"x":351,"y":358,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,376,377],{"x":354,"y":371,"style":372},"SKU, Product, Category, Notes",[320,379],{"x":329,"y":380,"width":331,"height":359,"rx":323,"fill":360,"stroke":333,"style":334},"90.0",[343,382,384],{"x":363,"y":383,"style":365},"112.0","numbers",[320,386],{"x":337,"y":380,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,388,283],{"x":345,"y":389,"style":372},"111.0",[320,391],{"x":351,"y":380,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,393,394],{"x":354,"y":389,"style":372},"all 18 monthly columns",[320,396],{"x":329,"y":397,"width":331,"height":359,"rx":323,"fill":360,"stroke":333,"style":334},"124.0",[343,399,401],{"x":363,"y":400,"style":365},"146.0","sales block",[320,403],{"x":337,"y":397,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,405,407],{"x":345,"y":406,"style":372},"145.0","cs.starts_with('Sales ')",[320,409],{"x":351,"y":397,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,411,412],{"x":354,"y":406,"style":372},"Sales Jan … Sales Sep (for now)",[320,414],{"x":329,"y":415,"width":331,"height":359,"rx":323,"fill":360,"stroke":333,"style":334},"158.0",[343,417,419],{"x":363,"y":418,"style":365},"180.0","second quarter",[320,421],{"x":337,"y":415,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,423,425],{"x":345,"y":424,"style":372},"179.0","cs.matches(' (Apr|May|Jun)$')",[320,427],{"x":351,"y":415,"width":338,"height":359,"rx":323,"fill":326,"stroke":333,"style":334},[343,429,430],{"x":354,"y":424,"style":372},"6 columns, both blocks",[30,432,434],{"id":433},"step-2-clean-every-text-column-and-fill-every-numeric-gap","Step 2 — Clean every text column, and fill every numeric gap",[10,436,437,438,441],{},"Selectors go anywhere a column expression goes, so a single ",[19,439,440],{},"with_columns"," call can clean whole groups of columns:",[35,443,445],{"className":81,"code":444,"language":83,"meta":40,"style":40},"clean = df.with_columns(\n    (cs.string() - cs.by_name(\"Category\")).str.strip_chars(),   # trim the other text columns\n    pl.col(\"Category\").str.strip_chars().str.to_titlecase(),\n    cs.numeric().fill_null(0),                         # blank month = no sales \u002F returns\n)\nprint(clean.select(\"Product\", \"Category\").to_dicts()[:4])\nprint(\"nulls left:\", clean.null_count().sum_horizontal().item())\n",[19,446,447,457,478,488,501,505,528],{"__ignoreMap":40},[44,448,449,452,454],{"class":46,"line":47},[44,450,451],{"class":94},"clean ",[44,453,130],{"class":90},[44,455,456],{"class":94}," df.with_columns(\n",[44,458,459,462,465,468,471,474],{"class":46,"line":104},[44,460,461],{"class":94},"    (cs.string() ",[44,463,464],{"class":90},"-",[44,466,467],{"class":94}," cs.by_name(",[44,469,470],{"class":54},"\"Category\"",[44,472,473],{"class":94},")).str.strip_chars(),   ",[44,475,477],{"class":476},"s-wDw","# trim the other text columns\n",[44,479,480,483,485],{"class":46,"line":117},[44,481,482],{"class":94},"    pl.col(",[44,484,470],{"class":54},[44,486,487],{"class":94},").str.strip_chars().str.to_titlecase(),\n",[44,489,490,493,495,498],{"class":46,"line":124},[44,491,492],{"class":94},"    cs.numeric().fill_null(",[44,494,323],{"class":145},[44,496,497],{"class":94},"),                         ",[44,499,500],{"class":476},"# blank month = no sales \u002F returns\n",[44,502,503],{"class":46,"line":142},[44,504,139],{"class":94},[44,506,507,509,512,515,518,520,523,525],{"class":46,"line":152},[44,508,146],{"class":145},[44,510,511],{"class":94},"(clean.select(",[44,513,514],{"class":54},"\"Product\"",[44,516,517],{"class":94},", ",[44,519,470],{"class":54},[44,521,522],{"class":94},").to_dicts()[:",[44,524,322],{"class":145},[44,526,527],{"class":94},"])\n",[44,529,530,532,534,537],{"class":46,"line":167},[44,531,146],{"class":145},[44,533,215],{"class":94},[44,535,536],{"class":54},"\"nulls left:\"",[44,538,539],{"class":94},", clean.null_count().sum_horizontal().item())\n",[10,541,542,545,546,549,550,554,555,557,558,560,561,564,565,568,569,571],{},[19,543,544],{},"(cs.string() - cs.by_name(\"Category\")).str.strip_chars()"," trims spaces from every text column except Category, which gets its own expression that also fixes the capitalisation, so \"tools\" and \"garden \" become \"Tools\" and \"Garden\". ",[19,547,548],{},"cs.numeric().fill_null(0)"," fills the three blank monthly cells. The four nulls that remain are in Notes: calamine reads empty text cells as null, not as empty strings, which is usually what you want for a notes column. Filling with zero is a decision about the data — here a blank month means nothing was sold or returned; if a blank means \"not reported\", leave it null instead (see ",[24,551,553],{"href":552},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fhow-missing-values-change-sums-averages-and-counts\u002F","How Missing Values Change Sums, Averages and Counts","). Why subtract Category from the string selector instead of letting the specific expression win? Because Polars does not let one ",[19,556,440],{}," call produce the same column twice: with plain ",[19,559,287],{}," ",[14,562,563],{},"and"," a Category expression in the same call, it raises ",[19,566,567],{},"ComputeError: the name 'Category' … is duplicate",". That strictness is helpful — it means two rules can never silently compete for a column — and set subtraction is the idiomatic way to say \"all text columns except this one\". The alternative is two chained ",[19,570,440],{}," calls, where the second one overwrites the first.",[30,573,575],{"id":574},"step-3-totals-across-a-block-with-set-operations","Step 3 — Totals across a block with set operations",[10,577,578,579,582],{},"Row totals across a block of columns are where selectors save the most typing. ",[19,580,581],{},"pl.sum_horizontal"," adds across columns, and a selector supplies the columns:",[35,584,586],{"className":81,"code":585,"language":83,"meta":40,"style":40},"SALES_M = cs.matches(r\"^Sales [A-Z][a-z]{2}$\")          # Sales Jan … Sales Sep, nothing else\nRETURNS_M = cs.matches(r\"^Returns [A-Z][a-z]{2}$\")\ntotals = clean.with_columns(\n    pl.sum_horizontal(SALES_M).alias(\"Sales YTD\"),\n    pl.sum_horizontal(RETURNS_M).alias(\"Returns YTD\"),\n    pl.mean_horizontal(SALES_M & cs.matches(r\"(Jul|Aug|Sep)$\")).round(1).alias(\"Sales Q3 avg\"),\n).with_columns(\n    (pl.col(\"Returns YTD\") \u002F pl.col(\"Sales YTD\") * 100).round(1).alias(\"Return rate %\"),\n)\nprint(totals.select(\"SKU\", \"Sales YTD\", \"Returns YTD\", \"Sales Q3 avg\", \"Return rate %\"))\n",[19,587,588,638,674,684,699,712,760,765,803,807],{"__ignoreMap":40},[44,589,590,593,596,599,601,603,606,609,612,615,618,621,624,627,630,632,635],{"class":46,"line":47},[44,591,592],{"class":145},"SALES_M",[44,594,595],{"class":90}," =",[44,597,598],{"class":94}," cs.matches(",[44,600,209],{"class":90},[44,602,235],{"class":54},[44,604,605],{"class":145},"^",[44,607,608],{"class":54},"Sales ",[44,610,611],{"class":145},"[",[44,613,614],{"class":259},"A-Z",[44,616,617],{"class":145},"][",[44,619,620],{"class":259},"a-z",[44,622,623],{"class":145},"]",[44,625,626],{"class":90},"{2}",[44,628,629],{"class":145},"$",[44,631,235],{"class":54},[44,633,634],{"class":94},")          ",[44,636,637],{"class":476},"# Sales Jan … Sales Sep, nothing else\n",[44,639,640,643,645,647,649,651,653,656,658,660,662,664,666,668,670,672],{"class":46,"line":104},[44,641,642],{"class":145},"RETURNS_M",[44,644,595],{"class":90},[44,646,598],{"class":94},[44,648,209],{"class":90},[44,650,235],{"class":54},[44,652,605],{"class":145},[44,654,655],{"class":54},"Returns ",[44,657,611],{"class":145},[44,659,614],{"class":259},[44,661,617],{"class":145},[44,663,620],{"class":259},[44,665,623],{"class":145},[44,667,626],{"class":90},[44,669,629],{"class":145},[44,671,235],{"class":54},[44,673,139],{"class":94},[44,675,676,679,681],{"class":46,"line":117},[44,677,678],{"class":94},"totals ",[44,680,130],{"class":90},[44,682,683],{"class":94}," clean.with_columns(\n",[44,685,686,689,691,694,697],{"class":46,"line":124},[44,687,688],{"class":94},"    pl.sum_horizontal(",[44,690,592],{"class":145},[44,692,693],{"class":94},").alias(",[44,695,696],{"class":54},"\"Sales YTD\"",[44,698,197],{"class":94},[44,700,701,703,705,707,710],{"class":46,"line":142},[44,702,688],{"class":94},[44,704,642],{"class":145},[44,706,693],{"class":94},[44,708,709],{"class":54},"\"Returns YTD\"",[44,711,197],{"class":94},[44,713,714,717,719,722,724,726,728,730,733,735,738,740,743,745,747,750,753,755,758],{"class":46,"line":152},[44,715,716],{"class":94},"    pl.mean_horizontal(",[44,718,592],{"class":145},[44,720,721],{"class":90}," &",[44,723,598],{"class":94},[44,725,209],{"class":90},[44,727,235],{"class":54},[44,729,215],{"class":145},[44,731,732],{"class":54},"Jul",[44,734,221],{"class":90},[44,736,737],{"class":54},"Aug",[44,739,221],{"class":90},[44,741,742],{"class":54},"Sep",[44,744,232],{"class":145},[44,746,235],{"class":54},[44,748,749],{"class":94},")).round(",[44,751,752],{"class":145},"1",[44,754,693],{"class":94},[44,756,757],{"class":54},"\"Sales Q3 avg\"",[44,759,197],{"class":94},[44,761,762],{"class":46,"line":167},[44,763,764],{"class":94},").with_columns(\n",[44,766,767,770,772,775,778,781,783,785,788,791,794,796,798,801],{"class":46,"line":176},[44,768,769],{"class":94},"    (pl.col(",[44,771,709],{"class":54},[44,773,774],{"class":94},") ",[44,776,777],{"class":90},"\u002F",[44,779,780],{"class":94}," pl.col(",[44,782,696],{"class":54},[44,784,774],{"class":94},[44,786,787],{"class":90},"*",[44,789,790],{"class":145}," 100",[44,792,793],{"class":94},").round(",[44,795,752],{"class":145},[44,797,693],{"class":94},[44,799,800],{"class":54},"\"Return rate %\"",[44,802,197],{"class":94},[44,804,805],{"class":46,"line":185},[44,806,139],{"class":94},[44,808,809,811,814,817,819,821,823,825,827,829,831,833],{"class":46,"line":200},[44,810,146],{"class":145},[44,812,813],{"class":94},"(totals.select(",[44,815,816],{"class":54},"\"SKU\"",[44,818,517],{"class":94},[44,820,696],{"class":54},[44,822,517],{"class":94},[44,824,709],{"class":54},[44,826,517],{"class":94},[44,828,757],{"class":54},[44,830,517],{"class":94},[44,832,800],{"class":54},[44,834,835],{"class":94},"))\n",[10,837,838,839,842,843,845,846,848,849,852,853,856,857,859,860,863,864,867,868,870,871,874,875,878],{},"Selectors combine with set operators: ",[19,840,841],{},"&"," (columns in both), ",[19,844,221],{}," (either), ",[19,847,464],{}," (in the first but not the second) and ",[19,850,851],{},"~"," (everything except). ",[19,854,855],{},"SALES_M & cs.matches(r\"(Jul|Aug|Sep)$\")"," means \"sales columns for the third quarter\", without listing them. Note the precise monthly selectors defined first: once a \"Sales YTD\" column exists, ",[19,858,291],{}," matches it too, so a later total over ",[19,861,862],{},"starts_with(\"Sales \")"," would count the year-to-date figure on top of the months — and a ",[19,865,866],{},"select"," that combines ",[19,869,862],{}," with ",[19,872,873],{},"ends_with(\"YTD\")"," fails with Polars' ",[19,876,877],{},"DuplicateError",", because both pick \"Sales YTD\". A pattern that describes exactly the monthly columns (\"Sales\", a space, a three-letter month) avoids both. Because the totals use selectors, they stay right when months are added; with hand-written column lists they would silently omit the new month.",[301,880,310,885,310,888,310,891,310,894,310,902,310,913,310,918,310,923,310,927,310,931,310,935,310,940,310,947,310,952,310,956,310,959,310,962],{"viewBox":881,"role":304,"ariaLabelledBy":882,"xmlns":308,"style":309},"4 0 752 207",[883,884],"sel-set-t","sel-set-d",[312,886,887],{"id":883},"Listing columns by hand versus combining selectors",[316,889,890],{"id":884},"A hand-written list of the third-quarter sales columns must be edited when columns change and silently misses new ones. Combining a starts_with selector with a regular-expression selector using the and operator describes the same columns and adapts to the sheet.",[320,892],{"x":322,"y":323,"width":324,"height":893,"fill":326},"207",[320,895],{"x":329,"y":330,"width":896,"height":897,"rx":898,"fill":899,"stroke":900,"style":901},"351.0","175.0","14","#fce9e9","var(--danger,#dc2626)","stroke-width:2px",[903,904,906],"g",{"transform":905},"translate(182.5,29.0) scale(1.083)",[907,908],"path",{"d":909,"fill":910,"stroke":911,"style":912},"M6 6l12 12 M18 6L6 18","none","var(--accent-ink,#be185d)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[343,914,917],{"x":915,"y":364,"style":916},"195.5","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","hand-written list",[46,919],{"x1":920,"y1":921,"x2":922,"y2":921,"stroke":900,"style":334},"36.0","91.0","355.0",[343,924,926],{"x":915,"y":389,"style":925},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","['Sales Jul', 'Sales Aug', 'Sales Sep']",[343,928,930],{"x":915,"y":929,"style":925},"137.0","edited every quarter",[343,932,934],{"x":915,"y":933,"style":925},"163.0","new columns silently missed",[320,936],{"x":937,"y":330,"width":896,"height":897,"rx":898,"fill":938,"stroke":939,"style":901},"389.0","#d9f4f1","var(--teal,#0f9488)",[903,941,943],{"transform":942},"translate(551.5,29.0) scale(1.083)",[907,944],{"d":945,"fill":910,"stroke":946,"style":912},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[343,948,951],{"x":949,"y":364,"style":950},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","selector expression",[46,953],{"x1":954,"y1":921,"x2":955,"y2":921,"stroke":939,"style":334},"405.0","724.0",[343,957,958],{"x":949,"y":389,"style":925},"SALES_M & matches('(Jul|Aug|Sep)$')",[343,960,961],{"x":949,"y":929,"style":925},"describes the rule, not the names",[343,963,964],{"x":949,"y":933,"style":925},"adapts to the sheet",[30,966,968],{"id":967},"step-4-rename-a-whole-block","Step 4 — Rename a whole block",[10,970,971,972,975],{},"Renaming many columns by rule is another job for selectors. ",[19,973,974],{},"name.map"," applies a function to the names of the selected columns:",[35,977,979],{"className":81,"code":978,"language":83,"meta":40,"style":40},"renamed = totals.select(\n    cs.by_name(\"SKU\", \"Product\", \"Category\"),\n    SALES_M.name.map(lambda n: n.replace(\"Sales \", \"\") + \" sales\"),\n    RETURNS_M.name.map(lambda n: n.replace(\"Returns \", \"\") + \" returns\"),\n    cs.ends_with(\"YTD\", \"avg\", \"%\"),\n)\nprint(renamed.columns[:6], \"…\", renamed.columns[-4:])\nprint(\"dropped:\", sorted(set(totals.columns) - set(totals.select(cs.all() - cs.by_name(\"Notes\")).columns)))\n",[19,980,981,991,1008,1039,1066,1086,1090,1116],{"__ignoreMap":40},[44,982,983,986,988],{"class":46,"line":47},[44,984,985],{"class":94},"renamed ",[44,987,130],{"class":90},[44,989,990],{"class":94}," totals.select(\n",[44,992,993,996,998,1000,1002,1004,1006],{"class":46,"line":104},[44,994,995],{"class":94},"    cs.by_name(",[44,997,816],{"class":54},[44,999,517],{"class":94},[44,1001,514],{"class":54},[44,1003,517],{"class":94},[44,1005,470],{"class":54},[44,1007,197],{"class":94},[44,1009,1010,1013,1016,1019,1022,1024,1026,1029,1031,1034,1037],{"class":46,"line":117},[44,1011,1012],{"class":145},"    SALES_M",[44,1014,1015],{"class":94},".name.map(",[44,1017,1018],{"class":90},"lambda",[44,1020,1021],{"class":94}," n: n.replace(",[44,1023,194],{"class":54},[44,1025,517],{"class":94},[44,1027,1028],{"class":54},"\"\"",[44,1030,774],{"class":94},[44,1032,1033],{"class":90},"+",[44,1035,1036],{"class":54}," \" sales\"",[44,1038,197],{"class":94},[44,1040,1041,1044,1046,1048,1050,1053,1055,1057,1059,1061,1064],{"class":46,"line":124},[44,1042,1043],{"class":145},"    RETURNS_M",[44,1045,1015],{"class":94},[44,1047,1018],{"class":90},[44,1049,1021],{"class":94},[44,1051,1052],{"class":54},"\"Returns \"",[44,1054,517],{"class":94},[44,1056,1028],{"class":54},[44,1058,774],{"class":94},[44,1060,1033],{"class":90},[44,1062,1063],{"class":54}," \" returns\"",[44,1065,197],{"class":94},[44,1067,1068,1071,1074,1076,1079,1081,1084],{"class":46,"line":142},[44,1069,1070],{"class":94},"    cs.ends_with(",[44,1072,1073],{"class":54},"\"YTD\"",[44,1075,517],{"class":94},[44,1077,1078],{"class":54},"\"avg\"",[44,1080,517],{"class":94},[44,1082,1083],{"class":54},"\"%\"",[44,1085,197],{"class":94},[44,1087,1088],{"class":46,"line":152},[44,1089,139],{"class":94},[44,1091,1092,1094,1097,1100,1103,1106,1109,1111,1113],{"class":46,"line":167},[44,1093,146],{"class":145},[44,1095,1096],{"class":94},"(renamed.columns[:",[44,1098,1099],{"class":145},"6",[44,1101,1102],{"class":94},"], ",[44,1104,1105],{"class":54},"\"…\"",[44,1107,1108],{"class":94},", renamed.columns[",[44,1110,464],{"class":90},[44,1112,322],{"class":145},[44,1114,1115],{"class":94},":])\n",[44,1117,1118,1120,1122,1125,1127,1130,1132,1135,1138,1140,1143,1146,1148,1150,1153],{"class":46,"line":176},[44,1119,146],{"class":145},[44,1121,215],{"class":94},[44,1123,1124],{"class":54},"\"dropped:\"",[44,1126,517],{"class":94},[44,1128,1129],{"class":145},"sorted",[44,1131,215],{"class":94},[44,1133,1134],{"class":145},"set",[44,1136,1137],{"class":94},"(totals.columns) ",[44,1139,464],{"class":90},[44,1141,1142],{"class":145}," set",[44,1144,1145],{"class":94},"(totals.select(cs.all() ",[44,1147,464],{"class":90},[44,1149,467],{"class":94},[44,1151,1152],{"class":54},"\"Notes\"",[44,1154,1155],{"class":94},")).columns)))\n",[10,1157,1158,1159,1161,1162,1165],{},"The ",[19,1160,866],{}," keeps the descriptive columns, renames both monthly blocks — \"Sales Jan\" becomes \"Jan sales\" — and keeps the totals, in the order listed. The Notes column is not selected, so it is dropped; ",[19,1163,1164],{},"cs.all() - cs.by_name(\"Notes\")"," shows the same set expressed as \"everything except Notes\". Selecting columns this way is also a convenient final step before writing: it fixes the output layout explicitly without spelling out every name.",[30,1167,1169],{"id":1168},"step-5-unpivot-a-block-into-a-long-table","Step 5 — Unpivot a block into a long table",[10,1171,1172],{},"Wide sheets are for people; long tables are for analysis and pivots. Unpivot both monthly blocks, then split the old column names into month and measure:",[35,1174,1176],{"className":81,"code":1175,"language":83,"meta":40,"style":40},"long = (\n    clean.unpivot(index=[\"SKU\", \"Product\", \"Category\"], on=SALES_M | RETURNS_M,\n                  variable_name=\"Column\", value_name=\"Units\")\n    .with_columns(pl.col(\"Column\").str.split_exact(\" \", 1).struct.rename_fields([\"Measure\", \"Month\"]))\n    .unnest(\"Column\")\n    .pivot(on=\"Measure\", index=[\"SKU\", \"Product\", \"Category\", \"Month\"], values=\"Units\")\n)\nprint(long.shape, long.columns)\nprint(long.filter(pl.col(\"SKU\") == \"DRL-01\").head(3))\n",[19,1177,1178,1189,1229,1251,1282,1291,1335,1339,1355],{"__ignoreMap":40},[44,1179,1180,1184,1186],{"class":46,"line":47},[44,1181,1183],{"class":1182},"sa561","long",[44,1185,595],{"class":90},[44,1187,1188],{"class":94}," (\n",[44,1190,1191,1194,1197,1199,1201,1203,1205,1207,1209,1211,1213,1216,1218,1220,1223,1226],{"class":46,"line":104},[44,1192,1193],{"class":94},"    clean.unpivot(",[44,1195,1196],{"class":1182},"index",[44,1198,130],{"class":90},[44,1200,611],{"class":94},[44,1202,816],{"class":54},[44,1204,517],{"class":94},[44,1206,514],{"class":54},[44,1208,517],{"class":94},[44,1210,470],{"class":54},[44,1212,1102],{"class":94},[44,1214,1215],{"class":1182},"on",[44,1217,130],{"class":90},[44,1219,592],{"class":145},[44,1221,1222],{"class":90}," |",[44,1224,1225],{"class":145}," RETURNS_M",[44,1227,1228],{"class":94},",\n",[44,1230,1231,1234,1236,1239,1241,1244,1246,1249],{"class":46,"line":117},[44,1232,1233],{"class":1182},"                  variable_name",[44,1235,130],{"class":90},[44,1237,1238],{"class":54},"\"Column\"",[44,1240,517],{"class":94},[44,1242,1243],{"class":1182},"value_name",[44,1245,130],{"class":90},[44,1247,1248],{"class":54},"\"Units\"",[44,1250,139],{"class":94},[44,1252,1253,1256,1258,1261,1264,1266,1268,1271,1274,1276,1279],{"class":46,"line":124},[44,1254,1255],{"class":94},"    .with_columns(pl.col(",[44,1257,1238],{"class":54},[44,1259,1260],{"class":94},").str.split_exact(",[44,1262,1263],{"class":54},"\" \"",[44,1265,517],{"class":94},[44,1267,752],{"class":145},[44,1269,1270],{"class":94},").struct.rename_fields([",[44,1272,1273],{"class":54},"\"Measure\"",[44,1275,517],{"class":94},[44,1277,1278],{"class":54},"\"Month\"",[44,1280,1281],{"class":94},"]))\n",[44,1283,1284,1287,1289],{"class":46,"line":142},[44,1285,1286],{"class":94},"    .unnest(",[44,1288,1238],{"class":54},[44,1290,139],{"class":94},[44,1292,1293,1296,1298,1300,1302,1304,1306,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1329,1331,1333],{"class":46,"line":152},[44,1294,1295],{"class":94},"    .pivot(",[44,1297,1215],{"class":1182},[44,1299,130],{"class":90},[44,1301,1273],{"class":54},[44,1303,517],{"class":94},[44,1305,1196],{"class":1182},[44,1307,130],{"class":90},[44,1309,611],{"class":94},[44,1311,816],{"class":54},[44,1313,517],{"class":94},[44,1315,514],{"class":54},[44,1317,517],{"class":94},[44,1319,470],{"class":54},[44,1321,517],{"class":94},[44,1323,1278],{"class":54},[44,1325,1102],{"class":94},[44,1327,1328],{"class":1182},"values",[44,1330,130],{"class":90},[44,1332,1248],{"class":54},[44,1334,139],{"class":94},[44,1336,1337],{"class":46,"line":167},[44,1338,139],{"class":94},[44,1340,1341,1343,1345,1347,1350,1352],{"class":46,"line":176},[44,1342,146],{"class":145},[44,1344,215],{"class":94},[44,1346,1183],{"class":1182},[44,1348,1349],{"class":94},".shape, ",[44,1351,1183],{"class":1182},[44,1353,1354],{"class":94},".columns)\n",[44,1356,1357,1359,1361,1363,1366,1368,1370,1373,1376,1379,1382],{"class":46,"line":185},[44,1358,146],{"class":145},[44,1360,215],{"class":94},[44,1362,1183],{"class":1182},[44,1364,1365],{"class":94},".filter(pl.col(",[44,1367,816],{"class":54},[44,1369,774],{"class":94},[44,1371,1372],{"class":90},"==",[44,1374,1375],{"class":54}," \"DRL-01\"",[44,1377,1378],{"class":94},").head(",[44,1380,1381],{"class":145},"3",[44,1383,835],{"class":94},[10,1385,1386,1389,1390,1393,1394,28],{},[19,1387,1388],{},"unpivot"," turns the 18 monthly columns into rows — selected with one selector — and ",[19,1391,1392],{},"str.split_exact"," splits \"Sales Jan\" into a measure and a month. Pivoting on the measure gives one row per product and month with Sales and Returns side by side: 6 products × 9 months = 54 rows. This long shape feeds straight into group-bys, charts and Excel pivot tables. The pandas equivalent is described in ",[24,1395,1397],{"href":1396},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Funpivot-a-wide-excel-sheet-with-pandas-melt\u002F","Unpivot a Wide Excel Sheet with pandas melt",[301,1399,310,1405,310,1408,310,1411,310,1415,310,1418,310,1422,310,1428,310,1432,310,1436,310,1440,310,1444,310,1448,310,1452,310,1456,310,1459,310,1461,310,1465,310,1467,310,1470,310,1472,310,1475,310,1477,310,1480,310,1483,310,1486,310,1488,310,1492,310,1494,310,1497,310,1499,310,1502,310,1504,310,1506,310,1510,310,1514,310,1519,310,1523,310,1527,310,1531,310,1535,310,1539,310,1542,310,1545,310,1547,310,1551,310,1553,310,1555,310,1557,310,1559,310,1562,310,1565,310,1567,310,1570,310,1572,310,1574,310,1576,310,1579,310,1585,310,1588,310,1593,310,1598,310,1605],{"viewBox":1400,"role":304,"ariaLabelledBy":1401,"xmlns":308,"style":1404},"3 0 753 140",[1402,1403],"sel-long-t","sel-long-d","width:100%;max-width:753px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[312,1406,1407],{"id":1402},"From one wide row to rows per month",[316,1409,1410],{"id":1403},"A product row with Sales Jan, Sales Feb, Returns Jan and Returns Feb columns becomes one row per month with separate Sales and Returns columns, after unpivoting the selected blocks and pivoting on the measure.",[320,1412],{"x":1381,"y":323,"width":1413,"height":1414,"fill":326},"753","140",[320,1416],{"x":329,"y":332,"width":363,"height":1417,"rx":323,"fill":360,"stroke":333,"style":334},"24.0",[320,1419],{"x":1420,"y":332,"width":1421,"height":1417,"rx":323,"fill":360,"stroke":333,"style":334},"52.0","58.1",[343,1423,1427],{"x":1424,"y":1425,"style":1426},"81.1","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[320,1429],{"x":1430,"y":332,"width":1431,"height":1417,"rx":323,"fill":360,"stroke":333,"style":334},"110.1","70.1",[343,1433,1435],{"x":1434,"y":1425,"style":1426},"145.2","B",[320,1437],{"x":1438,"y":332,"width":1439,"height":1417,"rx":323,"fill":360,"stroke":333,"style":334},"180.3","70.4",[343,1441,1443],{"x":1442,"y":1425,"style":1426},"215.4","C",[320,1445],{"x":1446,"y":332,"width":1447,"height":1417,"rx":323,"fill":360,"stroke":333,"style":334},"250.6","62.9",[343,1449,1451],{"x":1450,"y":1425,"style":1426},"282.0","D",[320,1453],{"x":329,"y":1454,"width":363,"height":1455,"rx":323,"fill":360,"stroke":333,"style":334},"64.0","28.0",[343,1457,752],{"x":920,"y":1458,"style":1426},"83.0",[320,1460],{"x":1420,"y":1454,"width":1421,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1462,1464],{"x":1424,"y":1458,"style":1463},"font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","SKU",[320,1466],{"x":1430,"y":1454,"width":1431,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1468,1469],{"x":1434,"y":1458,"style":1463},"Sales Jan",[320,1471],{"x":1438,"y":1454,"width":1439,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1473,1474],{"x":1442,"y":1458,"style":1463},"Sales Feb",[320,1476],{"x":1446,"y":1454,"width":1447,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1478,1479],{"x":1450,"y":1458,"style":1463},"Ret. Jan",[320,1481],{"x":329,"y":1482,"width":363,"height":1455,"rx":323,"fill":360,"stroke":333,"style":334},"92.0",[343,1484,1485],{"x":920,"y":389,"style":1426},"2",[320,1487],{"x":1420,"y":1482,"width":1421,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1489,1491],{"x":1424,"y":389,"style":1490},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","DRL-01",[320,1493],{"x":1430,"y":1482,"width":1431,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1495,1496],{"x":1434,"y":389,"style":1490},"131",[320,1498],{"x":1438,"y":1482,"width":1439,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1500,1501],{"x":1442,"y":389,"style":1490},"70",[320,1503],{"x":1446,"y":1482,"width":1447,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1505,322],{"x":1450,"y":389,"style":1490},[320,1507],{"x":1508,"y":332,"width":1509,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},"514.8","58.6",[343,1511,1464],{"x":1512,"y":1513,"style":1463},"544.1","59.0",[320,1515],{"x":1516,"y":332,"width":1517,"height":1455,"rx":323,"fill":360,"stroke":333,"style":1518},"573.4","54.9","stroke-width:1.5px",[343,1520,1522],{"x":1521,"y":1513,"style":1463},"600.9","Month",[320,1524],{"x":1525,"y":332,"width":1526,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},"628.3","49.8",[343,1528,1530],{"x":1529,"y":1513,"style":1463},"653.2","Sales",[320,1532],{"x":1533,"y":332,"width":1534,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},"678.1","61.9",[343,1536,1538],{"x":1537,"y":1513,"style":1463},"709.1","Returns",[320,1540],{"x":1508,"y":1541,"width":1509,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},"68.0",[343,1543,1491],{"x":1512,"y":1544,"style":1490},"87.0",[320,1546],{"x":1516,"y":1541,"width":1517,"height":1455,"rx":323,"fill":938,"stroke":939,"style":1518},[343,1548,1550],{"x":1521,"y":1544,"style":1549},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Jan",[320,1552],{"x":1525,"y":1541,"width":1526,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1554,1496],{"x":1529,"y":1544,"style":1490},[320,1556],{"x":1533,"y":1541,"width":1534,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1558,322],{"x":1537,"y":1544,"style":1490},[320,1560],{"x":1508,"y":1561,"width":1509,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},"96.0",[343,1563,1491],{"x":1512,"y":1564,"style":1490},"115.0",[320,1566],{"x":1516,"y":1561,"width":1517,"height":1455,"rx":323,"fill":938,"stroke":939,"style":1518},[343,1568,1569],{"x":1521,"y":1564,"style":1549},"Feb",[320,1571],{"x":1525,"y":1561,"width":1526,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1573,1501],{"x":1529,"y":1564,"style":1490},[320,1575],{"x":1533,"y":1561,"width":1534,"height":1455,"rx":323,"fill":326,"stroke":333,"style":334},[343,1577,1578],{"x":1537,"y":1564,"style":1490},"…",[343,1580,1584],{"x":1581,"y":1582,"style":1583},"166.7","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","wide",[343,1586,1183],{"x":1587,"y":1582,"style":1583},"627.4",[46,1589],{"x1":1590,"y1":1591,"x2":1592,"y2":1591,"stroke":340,"style":901},"323.5","80.0","500.8",[1594,1595],"polygon",{"points":1596,"fill":1597},"504.8,80.0 495.8,85.0 495.8,75.0","#5b5cf0",[320,1599],{"x":1600,"y":1601,"width":1602,"height":1603,"rx":1604,"fill":326,"stroke":333,"style":341},"349.6","47.0","129.0","22.0","11",[343,1606,1610],{"x":1607,"y":1608,"style":1609},"414.1","62.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","unpivot + pivot",[30,1612,1614],{"id":1613},"writing-the-cleaned-sheet-back-to-excel","Writing the cleaned sheet back to Excel",[10,1616,1617,1618,1621,1622,1624,1625,1628,1629,1632],{},"Selectors also help on the way out. ",[19,1619,1620],{},"write_excel"," takes column names (or tuples of names) for its formatting options, and ",[19,1623,279],{}," turns any selector into exactly that, so the formats follow the same rules as the cleaning. Passing a combined selector such as ",[19,1626,1627],{},"SALES_M | cs.by_name(\"Sales YTD\")"," straight to ",[19,1630,1631],{},"column_totals"," fails in Polars 1.44 with \"the truth value of an Expr is ambiguous\" — expanding it to names first avoids that:",[35,1634,1636],{"className":81,"code":1635,"language":83,"meta":40,"style":40},"out = totals.select(cs.by_name(\"SKU\", \"Product\", \"Category\"), SALES_M, cs.ends_with(\"YTD\", \"%\"))\nnames = lambda sel: cs.expand_selector(out, sel)          # selector → tuple of column names\nout.write_excel(\n    \"sales_clean.xlsx\", worksheet=\"Sales\",\n    column_formats={names(SALES_M | cs.ends_with(\"YTD\")): \"#,##0\", names(cs.ends_with(\"%\")): \"0.0\"},\n    column_totals=list(names(SALES_M | cs.by_name(\"Sales YTD\"))),\n    autofit=True, freeze_panes=\"B2\",\n)\nprint(\"written:\", out.shape, \"| total row under\", len(cs.expand_selector(out, SALES_M | cs.by_name(\"Sales YTD\"))), \"columns\")\n",[19,1637,1638,1674,1690,1695,1712,1750,1774,1796,1800],{"__ignoreMap":40},[44,1639,1640,1643,1645,1648,1650,1652,1654,1656,1658,1661,1663,1666,1668,1670,1672],{"class":46,"line":47},[44,1641,1642],{"class":94},"out ",[44,1644,130],{"class":90},[44,1646,1647],{"class":94}," totals.select(cs.by_name(",[44,1649,816],{"class":54},[44,1651,517],{"class":94},[44,1653,514],{"class":54},[44,1655,517],{"class":94},[44,1657,470],{"class":54},[44,1659,1660],{"class":94},"), ",[44,1662,592],{"class":145},[44,1664,1665],{"class":94},", cs.ends_with(",[44,1667,1073],{"class":54},[44,1669,517],{"class":94},[44,1671,1083],{"class":54},[44,1673,835],{"class":94},[44,1675,1676,1679,1681,1684,1687],{"class":46,"line":104},[44,1677,1678],{"class":94},"names ",[44,1680,130],{"class":90},[44,1682,1683],{"class":90}," lambda",[44,1685,1686],{"class":94}," sel: cs.expand_selector(out, sel)          ",[44,1688,1689],{"class":476},"# selector → tuple of column names\n",[44,1691,1692],{"class":46,"line":117},[44,1693,1694],{"class":94},"out.write_excel(\n",[44,1696,1697,1700,1702,1705,1707,1710],{"class":46,"line":124},[44,1698,1699],{"class":54},"    \"sales_clean.xlsx\"",[44,1701,517],{"class":94},[44,1703,1704],{"class":1182},"worksheet",[44,1706,130],{"class":90},[44,1708,1709],{"class":54},"\"Sales\"",[44,1711,1228],{"class":94},[44,1713,1714,1717,1719,1722,1724,1726,1729,1731,1734,1737,1740,1742,1744,1747],{"class":46,"line":142},[44,1715,1716],{"class":1182},"    column_formats",[44,1718,130],{"class":90},[44,1720,1721],{"class":94},"{names(",[44,1723,592],{"class":145},[44,1725,1222],{"class":90},[44,1727,1728],{"class":94}," cs.ends_with(",[44,1730,1073],{"class":54},[44,1732,1733],{"class":94},")): ",[44,1735,1736],{"class":54},"\"#,##0\"",[44,1738,1739],{"class":94},", names(cs.ends_with(",[44,1741,1083],{"class":54},[44,1743,1733],{"class":94},[44,1745,1746],{"class":54},"\"0.0\"",[44,1748,1749],{"class":94},"},\n",[44,1751,1752,1755,1757,1760,1763,1765,1767,1769,1771],{"class":46,"line":152},[44,1753,1754],{"class":1182},"    column_totals",[44,1756,130],{"class":90},[44,1758,1759],{"class":145},"list",[44,1761,1762],{"class":94},"(names(",[44,1764,592],{"class":145},[44,1766,1222],{"class":90},[44,1768,467],{"class":94},[44,1770,696],{"class":54},[44,1772,1773],{"class":94},"))),\n",[44,1775,1776,1779,1781,1784,1786,1789,1791,1794],{"class":46,"line":167},[44,1777,1778],{"class":1182},"    autofit",[44,1780,130],{"class":90},[44,1782,1783],{"class":145},"True",[44,1785,517],{"class":94},[44,1787,1788],{"class":1182},"freeze_panes",[44,1790,130],{"class":90},[44,1792,1793],{"class":54},"\"B2\"",[44,1795,1228],{"class":94},[44,1797,1798],{"class":46,"line":176},[44,1799,139],{"class":94},[44,1801,1802,1804,1806,1809,1812,1815,1817,1820,1823,1825,1827,1829,1831,1834,1837],{"class":46,"line":185},[44,1803,146],{"class":145},[44,1805,215],{"class":94},[44,1807,1808],{"class":54},"\"written:\"",[44,1810,1811],{"class":94},", out.shape, ",[44,1813,1814],{"class":54},"\"| total row under\"",[44,1816,517],{"class":94},[44,1818,1819],{"class":145},"len",[44,1821,1822],{"class":94},"(cs.expand_selector(out, ",[44,1824,592],{"class":145},[44,1826,1222],{"class":90},[44,1828,467],{"class":94},[44,1830,696],{"class":54},[44,1832,1833],{"class":94},"))), ",[44,1835,1836],{"class":54},"\"columns\"",[44,1838,139],{"class":94},[10,1840,1841,1844,1845,1847,1848,1851,1852,1854,1855,28],{},[19,1842,1843],{},"column_formats"," maps each group of names to an Excel number format, so every monthly column and the year-to-date totals get thousands separators and the return rate one decimal place; ",[19,1846,1631],{}," adds the table's total row under the nine monthly sales columns and \"Sales YTD\", written as ",[19,1849,1850],{},"SUBTOTAL(109, [Sales Jan])"," formulas so they respect any filter a reader applies. When a new month appears, its column is formatted and totalled with no change to this code. More ",[19,1853,1620],{}," options are covered in ",[24,1856,1858],{"href":1857},"\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",[30,1860,1862],{"id":1861},"common-pitfalls","Common pitfalls",[1864,1865,1866,1876,1884,1890,1896],"ul",{},[1867,1868,1869,1875],"li",{},[1870,1871,1872,1873,28],"strong",{},"Two expressions for one column in a single ",[19,1874,440],{}," Polars raises a duplicate-name error; subtract the column from the broad selector or chain two calls.",[1867,1877,1878,560,1881,1883],{},[1870,1879,1880],{},"Prefix selectors that also catch derived columns.",[19,1882,862],{}," matches \"Sales YTD\" once it exists; use an exact pattern for the monthly block.",[1867,1885,1886,1889],{},[1870,1887,1888],{},"Filling every numeric blank with zero."," Only when a blank really means zero.",[1867,1891,1892,1895],{},[1870,1893,1894],{},"Hand-listed columns in totals."," They miss new months; use a selector.",[1867,1897,1898,1901,1902,1904],{},[1870,1899,1900],{},"Guessing what a selector picks."," Check with ",[19,1903,279],{}," first.",[30,1906,1908],{"id":1907},"conclusion","Conclusion",[10,1910,1911,1912,517,1914,1916,1917,517,1920,517,1923,1926,1927,1930,1931,517,1933,517,1935,1937,1938,1940,1941,1944,1945,1947,1948,1951],{},"Polars column selectors describe sets of columns by type (",[19,1913,283],{},[19,1915,287],{},"), name (",[19,1918,1919],{},"starts_with",[19,1921,1922],{},"ends_with",[19,1924,1925],{},"by_name",") or pattern (",[19,1928,1929],{},"matches","), and combine with ",[19,1932,841],{},[19,1934,221],{},[19,1936,464],{}," and ",[19,1939,851],{},". On wide Excel sheets they turn per-column cleaning into a few expressions: trim all text, fill all numeric gaps, total a whole block with ",[19,1942,1943],{},"sum_horizontal",", rename blocks with ",[19,1946,974],{},", and unpivot with ",[19,1949,1950],{},"on="," a selector — and because selectors are evaluated against the real columns, the code keeps working when next month's column appears.",[30,1953,1955],{"id":1954},"frequently-asked-questions","Frequently asked questions",[10,1957,1958,1961],{},[1870,1959,1960],{},"What are Polars column selectors?","\nFunctions in polars.selectors, usually imported as cs, that describe a set of columns by data type, name or pattern. They can be used wherever a column expression is accepted.",[10,1963,1964,1967],{},[1870,1965,1966],{},"How do I sum several Excel columns per row in Polars?","\nUse pl.sum_horizontal with a selector, for example a starts_with selector for the sales columns, which adds the selected columns row by row.",[10,1969,1970,1973],{},[1870,1971,1972],{},"How do I select all columns except some in Polars?","\nSubtract selectors, for example cs.all() - cs.by_name(\"Notes\"), or use ~ to negate a selector.",[10,1975,1976,1979],{},[1870,1977,1978],{},"How do I check which columns a selector matches?","\nCall cs.expand_selector(df, selector), which returns the names of the matching columns in that DataFrame.",[30,1981,1983],{"id":1982},"related","Related",[1864,1985,1986,1992,1999,2006],{},[1867,1987,1988,1989,1991],{},"Up one level: ",[24,1990,27],{"href":26}," — fast Excel reading with Polars.",[1867,1993,1994,1998],{},[24,1995,1997],{"href":1996},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-and-aggregate-excel-data-with-polars\u002F","Clean and Aggregate Excel Data with Polars"," — expressions and group-bys.",[1867,2000,2001,2005],{},[24,2002,2004],{"href":2003},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fjoin-and-pivot-excel-data-with-polars\u002F","Join and Pivot Excel Data with Polars"," — reshaping after cleaning.",[1867,2007,2008,2012],{},[24,2009,2011],{"href":2010},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas\u002F","Standardise Excel Column Names with pandas"," — renaming rules in pandas.",[2014,2015,2016],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":40,"searchDepth":104,"depth":104,"links":2018},[2019,2020,2021,2022,2023,2024,2025,2026,2027,2028,2029],{"id":32,"depth":104,"text":33},{"id":70,"depth":104,"text":71},{"id":433,"depth":104,"text":434},{"id":574,"depth":104,"text":575},{"id":967,"depth":104,"text":968},{"id":1168,"depth":104,"text":1169},{"id":1613,"depth":104,"text":1614},{"id":1861,"depth":104,"text":1862},{"id":1907,"depth":104,"text":1908},{"id":1954,"depth":104,"text":1955},{"id":1982,"depth":104,"text":1983},"2026-10-02","Wide Excel sheets with a column per month, region or measure are tedious to clean column by column. Polars column selectors pick columns by type, name pattern or position and combine with set operations, so one expression can trim every text column, fill every numeric gap, total every monthly column, rename a whole block and unpivot it — and keep working when next month adds a column.","md",[2034,2036,2038,2040],{"q":1960,"a":2035},"Functions in polars.selectors, usually imported as cs, that describe a set of columns by data type, name or pattern. They can be used wherever a column expression is accepted.",{"q":1966,"a":2037},"Use pl.sum_horizontal with a selector, for example a starts_with selector for the sales columns, which adds the selected columns row by row.",{"q":1972,"a":2039},"Subtract selectors, for example cs.all() - cs.by_name(\"Notes\"), or use ~ to negate a selector.",{"q":1978,"a":2041},"Call cs.expand_selector(df, selector), which returns the names of the matching columns in that DataFrame.","clean wide excel sheets with polars column selectors",null,{"breadcrumb":2045},[2046,2048,2051],{"name":2047,"item":777},"Home",{"name":2049,"item":2050},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":27,"item":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors",{"title":5,"description":2054},"Use polars.selectors on wide Excel data: numeric, string, starts_with, matches and set operations, applied to trimming, filling, row totals with sum_horizontal, bulk renaming and unpivot.","clean-wide-excel-sheets-with-polars-column-selectors","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-wide-excel-sheets-with-polars-column-selectors\u002Findex",[2058,2059],"polars","data-cleaning","how-to","tcBkwI2-bEivBD2zHVNotmsInOeNV7QRbg00SSjBBX0",[2063,2067],{"title":2064,"path":2065,"stem":2066,"children":-1},"Clean and Aggregate Excel Data with Polars Expressions","\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-and-aggregate-excel-data-with-polars","advanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fclean-and-aggregate-excel-data-with-polars\u002Findex",{"title":2068,"path":2069,"stem":2070,"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",[2072,2074],{"path":2073,"title":2049},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2075,"title":27},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow",[],[2078,2079,2082,2086,2088,2091],{"path":2065,"title":2064,"kind":2060},{"path":2080,"title":2081,"kind":2060},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fconvert-pandas-excel-code-to-polars","Convert pandas Excel Code to Polars",{"path":2083,"title":2084,"kind":2085},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Ffix-schema-and-type-errors-when-reading-excel-with-polars","Fix Schema and Type Errors When Reading Excel with Polars","fix",{"path":2087,"title":2004,"kind":2060},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fjoin-and-pivot-excel-data-with-polars",{"path":2089,"title":2090,"kind":2060},"\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":2092,"title":2093,"kind":2060},"\u002Fadvanced-data-transformation-and-cleaning\u002Freading-excel-with-polars-and-arrow\u002Fread-multiple-excel-sheets-with-polars","Read Multiple Excel Sheets with Polars",1790972098138]