[{"data":1,"prerenderedAt":2484},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas":2450,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas":2459,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas":2464,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas":2465},{"id":4,"title":5,"body":6,"dateModified":2418,"datePublished":2418,"description":2419,"extension":2420,"faq":2421,"keyword":2430,"kind":2431,"meta":2432,"navigation":161,"path":2440,"seo":2441,"slug":2444,"stem":2445,"topics":2446,"type":2448,"__hash__":2449},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas\u002Findex.md","CHOOSE, SWITCH and IFS Equivalents in pandas",{"type":7,"value":8,"toc":2405},"minimark",[9,60,65,96,106,110,125,615,647,651,656,802,820,824,837,949,970,1105,1109,1118,1257,1285,1463,1467,1473,1796,1822,1826,1839,2148,2161,2237,2241,2309,2313,2340,2344,2350,2356,2362,2368,2372,2401],[10,11,12,13,17,18,22,23,17,26,29,30,33,34,37,38,41,42,45,46,49,50,53,54,59],"p",{},"Spreadsheets pick one of several results in three different ways. ",[14,15,16],"code",{},"CHOOSE(2, \"Low\", \"Medium\", \"High\")"," picks by ",[19,20,21],"em",{},"position",". ",[14,24,25],{},"SWITCH(A2, \"N\", \"North\", \"S\", \"South\", \"Other\")",[19,27,28],{},"matching a value",", with a default. ",[14,31,32],{},"IFS(B2>=90, \"A\", B2>=75, \"B\", B2>=50, \"C\", TRUE, \"Fail\")"," picks by testing ",[19,35,36],{},"conditions in order"," and taking the first that is true. Deeply nested ",[14,39,40],{},"IF"," formulas do the same job less readably. In pandas, writing these with ",[14,43,44],{},"apply"," and a Python ",[14,47,48],{},"if"," chain works but runs row by row; each has a vectorised equivalent that is clearer and much faster. This page translates all three, adds banding with ",[14,51,52],{},"pd.cut"," for the common range case, deals with what happens when nothing matches — where the tools differ in important ways — and checks every translation against the spreadsheet formulas. It belongs to ",[55,56,58],"a",{"href":57},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[61,62,64],"h2",{"id":63},"prerequisites","Prerequisites",[66,67,72],"pre",{"className":68,"code":69,"language":70,"meta":71,"style":71},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas numpy openpyxl\n","bash","",[14,73,74],{"__ignoreMap":71},[75,76,79,83,87,90,93],"span",{"class":77,"line":78},"line",1,[75,80,82],{"class":81},"sMTad","pip",[75,84,86],{"class":85},"srMev"," install",[75,88,89],{"class":85}," pandas",[75,91,92],{"class":85}," numpy",[75,94,95],{"class":85}," openpyxl\n",[10,97,98,101,102,105],{},[14,99,100],{},"reps.xlsx"," lists eight sales reps with a one-letter region code (including an unknown \"X\"), a priority number from 1 to 4 (where 4 is out of range for a three-item list), and a score out of 100. LibreOffice (",[14,103,104],{},"soffice",") recalculates the equivalent spreadsheet formulas in Step 1 for comparison.",[61,107,109],{"id":108},"step-1-the-spreadsheet-formulas","Step 1 — The spreadsheet formulas",[10,111,112,113,116,117,120,121,124],{},"Write the three formulas for every row and recalculate, so each pandas version has a reference answer. ",[14,114,115],{},"SWITCH"," and ",[14,118,119],{},"IFS"," are newer functions, stored in the file with the ",[14,122,123],{},"_xlfn."," prefix:",[66,126,130],{"className":127,"code":128,"language":129,"meta":71,"style":71},"language-python shiki shiki-themes github-light github-dark-high-contrast","import subprocess\nfrom openpyxl import load_workbook\n\nwb = load_workbook(\"reps.xlsx\")\nws = wb.active\nws[\"E1\"], ws[\"F1\"], ws[\"G1\"] = \"CHOOSE\", \"SWITCH\", \"IFS\"\nfor r in range(2, ws.max_row + 1):\n    ws[f\"E{r}\"] = f'=CHOOSE(C{r},\"Urgent\",\"Normal\",\"Low\")'\n    ws[f\"F{r}\"] = f'=_xlfn.SWITCH(B{r},\"N\",\"North\",\"S\",\"South\",\"E\",\"East\",\"W\",\"West\",\"Unknown\")'\n    ws[f\"G{r}\"] = f'=_xlfn.IFS(D{r}>=90,\"A\",D{r}>=75,\"B\",D{r}>=50,\"C\",TRUE,\"Fail\")'\nwb.save(\"reps_formulas.xlsx\")\nsubprocess.run([\"soffice\", \"--headless\", \"--convert-to\", \"xlsx\", \"--outdir\", \"calc\", \"reps_formulas.xlsx\"], check=True, capture_output=True)\ncalc = load_workbook(\"calc\u002Freps_formulas.xlsx\", data_only=True).active\nexcel = [[c.value for c in row] for row in calc.iter_rows(min_row=2, min_col=5, max_col=7)]\nfor row, res in zip(calc.iter_rows(min_row=2, max_col=4, values_only=True), excel):\n    print(row, \"→\", res)\n","python",[14,131,132,142,156,163,181,192,231,265,309,345,399,410,471,496,557,600],{"__ignoreMap":71},[75,133,134,138],{"class":77,"line":78},[75,135,137],{"class":136},"s-kum","import",[75,139,141],{"class":140},"skGVy"," subprocess\n",[75,143,145,148,151,153],{"class":77,"line":144},2,[75,146,147],{"class":136},"from",[75,149,150],{"class":140}," openpyxl ",[75,152,137],{"class":136},[75,154,155],{"class":140}," load_workbook\n",[75,157,159],{"class":77,"line":158},3,[75,160,162],{"emptyLinePlaceholder":161},true,"\n",[75,164,166,169,172,175,178],{"class":77,"line":165},4,[75,167,168],{"class":140},"wb ",[75,170,171],{"class":136},"=",[75,173,174],{"class":140}," load_workbook(",[75,176,177],{"class":85},"\"reps.xlsx\"",[75,179,180],{"class":140},")\n",[75,182,184,187,189],{"class":77,"line":183},5,[75,185,186],{"class":140},"ws ",[75,188,171],{"class":136},[75,190,191],{"class":140}," wb.active\n",[75,193,195,198,201,204,207,209,212,215,217,220,223,226,228],{"class":77,"line":194},6,[75,196,197],{"class":140},"ws[",[75,199,200],{"class":85},"\"E1\"",[75,202,203],{"class":140},"], ws[",[75,205,206],{"class":85},"\"F1\"",[75,208,203],{"class":140},[75,210,211],{"class":85},"\"G1\"",[75,213,214],{"class":140},"] ",[75,216,171],{"class":136},[75,218,219],{"class":85}," \"CHOOSE\"",[75,221,222],{"class":140},", ",[75,224,225],{"class":85},"\"SWITCH\"",[75,227,222],{"class":140},[75,229,230],{"class":85},"\"IFS\"\n",[75,232,234,237,240,243,247,250,253,256,259,262],{"class":77,"line":233},7,[75,235,236],{"class":136},"for",[75,238,239],{"class":140}," r ",[75,241,242],{"class":136},"in",[75,244,246],{"class":245},"sP0c6"," range",[75,248,249],{"class":140},"(",[75,251,252],{"class":245},"2",[75,254,255],{"class":140},", ws.max_row ",[75,257,258],{"class":136},"+",[75,260,261],{"class":245}," 1",[75,263,264],{"class":140},"):\n",[75,266,268,271,274,277,281,284,287,290,292,294,297,300,302,304,306],{"class":77,"line":267},8,[75,269,270],{"class":140},"    ws[",[75,272,273],{"class":136},"f",[75,275,276],{"class":85},"\"E",[75,278,280],{"class":279},"sSjpA","{",[75,282,283],{"class":140},"r",[75,285,286],{"class":279},"}",[75,288,289],{"class":85},"\"",[75,291,214],{"class":140},[75,293,171],{"class":136},[75,295,296],{"class":136}," f",[75,298,299],{"class":85},"'=CHOOSE(C",[75,301,280],{"class":279},[75,303,283],{"class":140},[75,305,286],{"class":279},[75,307,308],{"class":85},",\"Urgent\",\"Normal\",\"Low\")'\n",[75,310,312,314,316,319,321,323,325,327,329,331,333,336,338,340,342],{"class":77,"line":311},9,[75,313,270],{"class":140},[75,315,273],{"class":136},[75,317,318],{"class":85},"\"F",[75,320,280],{"class":279},[75,322,283],{"class":140},[75,324,286],{"class":279},[75,326,289],{"class":85},[75,328,214],{"class":140},[75,330,171],{"class":136},[75,332,296],{"class":136},[75,334,335],{"class":85},"'=_xlfn.SWITCH(B",[75,337,280],{"class":279},[75,339,283],{"class":140},[75,341,286],{"class":279},[75,343,344],{"class":85},",\"N\",\"North\",\"S\",\"South\",\"E\",\"East\",\"W\",\"West\",\"Unknown\")'\n",[75,346,348,350,352,355,357,359,361,363,365,367,369,372,374,376,378,381,383,385,387,390,392,394,396],{"class":77,"line":347},10,[75,349,270],{"class":140},[75,351,273],{"class":136},[75,353,354],{"class":85},"\"G",[75,356,280],{"class":279},[75,358,283],{"class":140},[75,360,286],{"class":279},[75,362,289],{"class":85},[75,364,214],{"class":140},[75,366,171],{"class":136},[75,368,296],{"class":136},[75,370,371],{"class":85},"'=_xlfn.IFS(D",[75,373,280],{"class":279},[75,375,283],{"class":140},[75,377,286],{"class":279},[75,379,380],{"class":85},">=90,\"A\",D",[75,382,280],{"class":279},[75,384,283],{"class":140},[75,386,286],{"class":279},[75,388,389],{"class":85},">=75,\"B\",D",[75,391,280],{"class":279},[75,393,283],{"class":140},[75,395,286],{"class":279},[75,397,398],{"class":85},">=50,\"C\",TRUE,\"Fail\")'\n",[75,400,402,405,408],{"class":77,"line":401},11,[75,403,404],{"class":140},"wb.save(",[75,406,407],{"class":85},"\"reps_formulas.xlsx\"",[75,409,180],{"class":140},[75,411,413,416,419,421,424,426,429,431,434,436,439,441,444,446,448,451,455,457,460,462,465,467,469],{"class":77,"line":412},12,[75,414,415],{"class":140},"subprocess.run([",[75,417,418],{"class":85},"\"soffice\"",[75,420,222],{"class":140},[75,422,423],{"class":85},"\"--headless\"",[75,425,222],{"class":140},[75,427,428],{"class":85},"\"--convert-to\"",[75,430,222],{"class":140},[75,432,433],{"class":85},"\"xlsx\"",[75,435,222],{"class":140},[75,437,438],{"class":85},"\"--outdir\"",[75,440,222],{"class":140},[75,442,443],{"class":85},"\"calc\"",[75,445,222],{"class":140},[75,447,407],{"class":85},[75,449,450],{"class":140},"], ",[75,452,454],{"class":453},"sa561","check",[75,456,171],{"class":136},[75,458,459],{"class":245},"True",[75,461,222],{"class":140},[75,463,464],{"class":453},"capture_output",[75,466,171],{"class":136},[75,468,459],{"class":245},[75,470,180],{"class":140},[75,472,474,477,479,481,484,486,489,491,493],{"class":77,"line":473},13,[75,475,476],{"class":140},"calc ",[75,478,171],{"class":136},[75,480,174],{"class":140},[75,482,483],{"class":85},"\"calc\u002Freps_formulas.xlsx\"",[75,485,222],{"class":140},[75,487,488],{"class":453},"data_only",[75,490,171],{"class":136},[75,492,459],{"class":245},[75,494,495],{"class":140},").active\n",[75,497,499,502,504,507,509,512,514,517,519,522,524,527,530,532,534,536,539,541,544,546,549,551,554],{"class":77,"line":498},14,[75,500,501],{"class":140},"excel ",[75,503,171],{"class":136},[75,505,506],{"class":140}," [[c.value ",[75,508,236],{"class":136},[75,510,511],{"class":140}," c ",[75,513,242],{"class":136},[75,515,516],{"class":140}," row] ",[75,518,236],{"class":136},[75,520,521],{"class":140}," row ",[75,523,242],{"class":136},[75,525,526],{"class":140}," calc.iter_rows(",[75,528,529],{"class":453},"min_row",[75,531,171],{"class":136},[75,533,252],{"class":245},[75,535,222],{"class":140},[75,537,538],{"class":453},"min_col",[75,540,171],{"class":136},[75,542,543],{"class":245},"5",[75,545,222],{"class":140},[75,547,548],{"class":453},"max_col",[75,550,171],{"class":136},[75,552,553],{"class":245},"7",[75,555,556],{"class":140},")]\n",[75,558,560,562,565,567,570,573,575,577,579,581,583,585,588,590,593,595,597],{"class":77,"line":559},15,[75,561,236],{"class":136},[75,563,564],{"class":140}," row, res ",[75,566,242],{"class":136},[75,568,569],{"class":245}," zip",[75,571,572],{"class":140},"(calc.iter_rows(",[75,574,529],{"class":453},[75,576,171],{"class":136},[75,578,252],{"class":245},[75,580,222],{"class":140},[75,582,548],{"class":453},[75,584,171],{"class":136},[75,586,587],{"class":245},"4",[75,589,222],{"class":140},[75,591,592],{"class":453},"values_only",[75,594,171],{"class":136},[75,596,459],{"class":245},[75,598,599],{"class":140},"), excel):\n",[75,601,603,606,609,612],{"class":77,"line":602},16,[75,604,605],{"class":245},"    print",[75,607,608],{"class":140},"(row, ",[75,610,611],{"class":85},"\"→\"",[75,613,614],{"class":140},", res)\n",[10,616,617,618,621,622,624,625,627,628,631,632,635,636,638,639,642,643,59],{},"The spreadsheet shows the behaviours to reproduce. ",[14,619,620],{},"CHOOSE"," returns an error for Grace, whose priority 4 has no fourth item. ",[14,623,115],{}," falls through to its default \"Unknown\" for Farid's code \"X\". ",[14,626,119],{}," tests the score thresholds in order, so 93 is an A, 75 a B — the boundary belongs to the higher band because ",[14,629,630],{},">="," is used — and anything below 50 reaches the final ",[14,633,634],{},"TRUE"," condition and gets \"Fail\". Writing the ",[14,637,123],{}," prefix matters: without it the spreadsheet does not recognise the newer functions and shows ",[14,640,641],{},"#NAME?",", as explained in ",[55,644,646],{"href":645},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-name-error-for-new-excel-functions-written-by-python\u002F","Fix #NAME? Errors for New Excel Functions Written by Python",[61,648,650],{"id":649},"step-2-choose-pick-by-position","Step 2 — CHOOSE: pick by position",[10,652,653,655],{},[14,654,620],{}," uses a 1-based position into a list. The pandas equivalent maps positions to values with a dictionary — which also makes the out-of-range case explicit:",[66,657,659],{"className":127,"code":658,"language":129,"meta":71,"style":71},"import pandas as pd\n\nreps = pd.read_excel(\"reps.xlsx\")\nlabels = [\"Urgent\", \"Normal\", \"Low\"]\nchoose_map = {i + 1: v for i, v in enumerate(labels)}\nreps[\"CHOOSE\"] = reps[\"Priority\"].map(choose_map)\nprint(reps[[\"Rep\", \"Priority\", \"CHOOSE\"]].to_string(index=False))\n",[14,660,661,674,678,692,718,748,769],{"__ignoreMap":71},[75,662,663,665,668,671],{"class":77,"line":78},[75,664,137],{"class":136},[75,666,667],{"class":140}," pandas ",[75,669,670],{"class":136},"as",[75,672,673],{"class":140}," pd\n",[75,675,676],{"class":77,"line":144},[75,677,162],{"emptyLinePlaceholder":161},[75,679,680,683,685,688,690],{"class":77,"line":158},[75,681,682],{"class":140},"reps ",[75,684,171],{"class":136},[75,686,687],{"class":140}," pd.read_excel(",[75,689,177],{"class":85},[75,691,180],{"class":140},[75,693,694,697,699,702,705,707,710,712,715],{"class":77,"line":165},[75,695,696],{"class":140},"labels ",[75,698,171],{"class":136},[75,700,701],{"class":140}," [",[75,703,704],{"class":85},"\"Urgent\"",[75,706,222],{"class":140},[75,708,709],{"class":85},"\"Normal\"",[75,711,222],{"class":140},[75,713,714],{"class":85},"\"Low\"",[75,716,717],{"class":140},"]\n",[75,719,720,723,725,728,730,732,735,737,740,742,745],{"class":77,"line":183},[75,721,722],{"class":140},"choose_map ",[75,724,171],{"class":136},[75,726,727],{"class":140}," {i ",[75,729,258],{"class":136},[75,731,261],{"class":245},[75,733,734],{"class":140},": v ",[75,736,236],{"class":136},[75,738,739],{"class":140}," i, v ",[75,741,242],{"class":136},[75,743,744],{"class":245}," enumerate",[75,746,747],{"class":140},"(labels)}\n",[75,749,750,753,756,758,760,763,766],{"class":77,"line":194},[75,751,752],{"class":140},"reps[",[75,754,755],{"class":85},"\"CHOOSE\"",[75,757,214],{"class":140},[75,759,171],{"class":136},[75,761,762],{"class":140}," reps[",[75,764,765],{"class":85},"\"Priority\"",[75,767,768],{"class":140},"].map(choose_map)\n",[75,770,771,774,777,780,782,784,786,788,791,794,796,799],{"class":77,"line":233},[75,772,773],{"class":245},"print",[75,775,776],{"class":140},"(reps[[",[75,778,779],{"class":85},"\"Rep\"",[75,781,222],{"class":140},[75,783,765],{"class":85},[75,785,222],{"class":140},[75,787,755],{"class":85},[75,789,790],{"class":140},"]].to_string(",[75,792,793],{"class":453},"index",[75,795,171],{"class":136},[75,797,798],{"class":245},"False",[75,800,801],{"class":140},"))\n",[10,803,804,807,808,811,812,815,816,819],{},[14,805,806],{},"Series.map"," with a dictionary of position → value gives \"Urgent\" for 1, \"Normal\" for 2, \"Low\" for 3 — and ",[14,809,810],{},"NaN"," for Grace's 4, where Excel showed an error. A missing value is the honest pandas result: it can be counted, filtered, or filled with ",[14,813,814],{},"fillna(\"Check priority\")",". Converting the list into a dictionary keyed from 1 avoids the off-by-one trap of indexing a Python list (which starts at 0) with Excel's 1-based numbers. ",[14,817,818],{},"numpy.choose"," also exists but raises an error for any out-of-range position, which is rarely what a report wants.",[61,821,823],{"id":822},"step-3-switch-pick-by-value-with-a-default","Step 3 — SWITCH: pick by value, with a default",[10,825,826,828,829,832,833,836],{},[14,827,115],{}," compares one expression with a list of values. In pandas that is ",[14,830,831],{},"map"," with a dictionary, followed by ",[14,834,835],{},"fillna"," for the default:",[66,838,840],{"className":127,"code":839,"language":129,"meta":71,"style":71},"REGIONS = {\"N\": \"North\", \"S\": \"South\", \"E\": \"East\", \"W\": \"West\"}\nreps[\"SWITCH\"] = reps[\"Region code\"].map(REGIONS).fillna(\"Unknown\")\nprint(reps[[\"Rep\", \"Region code\", \"SWITCH\"]].to_string(index=False))\n",[14,841,842,895,923],{"__ignoreMap":71},[75,843,844,847,850,853,856,859,862,864,867,869,872,874,877,879,882,884,887,889,892],{"class":77,"line":78},[75,845,846],{"class":245},"REGIONS",[75,848,849],{"class":136}," =",[75,851,852],{"class":140}," {",[75,854,855],{"class":85},"\"N\"",[75,857,858],{"class":140},": ",[75,860,861],{"class":85},"\"North\"",[75,863,222],{"class":140},[75,865,866],{"class":85},"\"S\"",[75,868,858],{"class":140},[75,870,871],{"class":85},"\"South\"",[75,873,222],{"class":140},[75,875,876],{"class":85},"\"E\"",[75,878,858],{"class":140},[75,880,881],{"class":85},"\"East\"",[75,883,222],{"class":140},[75,885,886],{"class":85},"\"W\"",[75,888,858],{"class":140},[75,890,891],{"class":85},"\"West\"",[75,893,894],{"class":140},"}\n",[75,896,897,899,901,903,905,907,910,913,915,918,921],{"class":77,"line":144},[75,898,752],{"class":140},[75,900,225],{"class":85},[75,902,214],{"class":140},[75,904,171],{"class":136},[75,906,762],{"class":140},[75,908,909],{"class":85},"\"Region code\"",[75,911,912],{"class":140},"].map(",[75,914,846],{"class":245},[75,916,917],{"class":140},").fillna(",[75,919,920],{"class":85},"\"Unknown\"",[75,922,180],{"class":140},[75,924,925,927,929,931,933,935,937,939,941,943,945,947],{"class":77,"line":158},[75,926,773],{"class":245},[75,928,776],{"class":140},[75,930,779],{"class":85},[75,932,222],{"class":140},[75,934,909],{"class":85},[75,936,222],{"class":140},[75,938,225],{"class":85},[75,940,790],{"class":140},[75,942,793],{"class":453},[75,944,171],{"class":136},[75,946,798],{"class":245},[75,948,801],{"class":140},[10,950,951,952,954,955,116,957,959,960,962,963,965,966,59],{},"Codes found in the dictionary are translated; Farid's \"X\" is not, so ",[14,953,831],{}," gives ",[14,956,810],{},[14,958,835],{}," supplies the default — exactly ",[14,961,115],{},"'s last argument. The dictionary is easier to maintain than a long formula: it can live in a settings file or be read from a mapping sheet, and adding a region is one entry. If no default is wanted, leave out ",[14,964,835],{}," and the unknown codes stay visible as missing values — often better than a catch-all label that hides data problems. Mapping tables at scale are covered in ",[55,967,969],{"href":968},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-category-values-with-a-mapping-table\u002F","Standardise Category Values with a Mapping Table",[971,972,980,981,980,985,980,989,980,996,980,1004,980,1011,980,1018,980,1021,980,1025,980,1028,980,1032,980,1037,980,1043,980,1045,980,1050,980,1052,980,1055,980,1057,980,1060,980,1063,980,1067,980,1069,980,1073,980,1075,980,1078,980,1080,980,1083,980,1086,980,1090,980,1092,980,1096,980,1098,980,1101,980,1103],"svg",{"viewBox":973,"role":974,"ariaLabelledBy":975,"xmlns":978,"style":979},"4 0 752 174","img",[976,977],"csi-map-t","csi-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  ",[982,983,984],"title",{"id":976},"Three ways to choose, in Excel and pandas",[986,987,988],"desc",{"id":977},"CHOOSE picks by 1-based position and becomes map with a position dictionary, giving NaN when out of range. SWITCH picks by value and becomes map with a dictionary plus fillna for the default. IFS picks by the first true condition and becomes numpy.select with ordered conditions and a default.",[990,991],"rect",{"x":587,"y":992,"width":993,"height":994,"fill":995},"0","752","174","#ffffff",[990,997],{"x":998,"y":999,"width":1000,"height":1001,"rx":992,"fill":995,"stroke":1002,"style":1003},"20.0","16.0","147.0","40.0","var(--line,#cdd5e6)","stroke-width:1px",[990,1005],{"x":1006,"y":999,"width":1007,"height":1001,"rx":992,"fill":1008,"stroke":1009,"style":1010},"167.0","191.0","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[1012,1013,1017],"text",{"x":1014,"y":1015,"style":1016},"262.5","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Excel",[990,1019],{"x":1020,"y":999,"width":1007,"height":1001,"rx":992,"fill":1008,"stroke":1009,"style":1010},"358.0",[1012,1022,1024],{"x":1023,"y":1015,"style":1016},"453.5","pandas",[990,1026],{"x":1027,"y":999,"width":1007,"height":1001,"rx":992,"fill":1008,"stroke":1009,"style":1010},"549.0",[1012,1029,1031],{"x":1030,"y":1015,"style":1016},"644.5","no match",[990,1033],{"x":998,"y":1034,"width":1000,"height":1035,"rx":992,"fill":1036,"stroke":1002,"style":1003},"56.0","34.0","#e7ebef",[1012,1038,1042],{"x":1039,"y":1040,"style":1041},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","by position",[990,1044],{"x":1006,"y":1034,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1046,1049],{"x":1014,"y":1047,"style":1048},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","CHOOSE(n, a, b, c)",[990,1051],{"x":1020,"y":1034,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1053,1054],{"x":1023,"y":1047,"style":1048},"map({1: a, 2: b, 3: c})",[990,1056],{"x":1027,"y":1034,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1058,1059],{"x":1030,"y":1047,"style":1048},"NaN (Excel: #VALUE!)",[990,1061],{"x":998,"y":1062,"width":1000,"height":1035,"rx":992,"fill":1036,"stroke":1002,"style":1003},"90.0",[1012,1064,1066],{"x":1039,"y":1065,"style":1041},"112.0","by value",[990,1068],{"x":1006,"y":1062,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1070,1072],{"x":1014,"y":1071,"style":1048},"111.0","SWITCH(x, k1, v1, …, default)",[990,1074],{"x":1020,"y":1062,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1076,1077],{"x":1023,"y":1071,"style":1048},"map(dict).fillna(default)",[990,1079],{"x":1027,"y":1062,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1081,1082],{"x":1030,"y":1071,"style":1048},"default",[990,1084],{"x":998,"y":1085,"width":1000,"height":1035,"rx":992,"fill":1036,"stroke":1002,"style":1003},"124.0",[1012,1087,1089],{"x":1039,"y":1088,"style":1041},"146.0","first true condition",[990,1091],{"x":1006,"y":1085,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1093,1095],{"x":1014,"y":1094,"style":1048},"145.0","IFS(c1, r1, c2, r2, TRUE, d)",[990,1097],{"x":1020,"y":1085,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1099,1100],{"x":1023,"y":1094,"style":1048},"np.select([c1, c2], [r1, r2], d)",[990,1102],{"x":1027,"y":1085,"width":1007,"height":1035,"rx":992,"fill":995,"stroke":1002,"style":1003},[1012,1104,1082],{"x":1030,"y":1094,"style":1048},[61,1106,1108],{"id":1107},"step-4-ifs-ordered-conditions-with-numpyselect","Step 4 — IFS: ordered conditions with numpy.select",[10,1110,1111,1113,1114,1117],{},[14,1112,119],{}," takes the first condition that is true. ",[14,1115,1116],{},"numpy.select"," does the same for whole columns: a list of conditions, a list of matching results, and a default for rows where none is true:",[66,1119,1121],{"className":127,"code":1120,"language":129,"meta":71,"style":71},"import numpy as np\n\nconditions = [reps[\"Score\"] >= 90, reps[\"Score\"] >= 75, reps[\"Score\"] >= 50]\ngrades = [\"A\", \"B\", \"C\"]\nreps[\"IFS\"] = np.select(conditions, grades, default=\"Fail\")\nprint(reps[[\"Rep\", \"Score\", \"IFS\"]].to_string(index=False))\n",[14,1122,1123,1135,1139,1184,1208,1231],{"__ignoreMap":71},[75,1124,1125,1127,1130,1132],{"class":77,"line":78},[75,1126,137],{"class":136},[75,1128,1129],{"class":140}," numpy ",[75,1131,670],{"class":136},[75,1133,1134],{"class":140}," np\n",[75,1136,1137],{"class":77,"line":144},[75,1138,162],{"emptyLinePlaceholder":161},[75,1140,1141,1144,1146,1149,1152,1154,1156,1159,1162,1164,1166,1168,1171,1173,1175,1177,1179,1182],{"class":77,"line":158},[75,1142,1143],{"class":140},"conditions ",[75,1145,171],{"class":136},[75,1147,1148],{"class":140}," [reps[",[75,1150,1151],{"class":85},"\"Score\"",[75,1153,214],{"class":140},[75,1155,630],{"class":136},[75,1157,1158],{"class":245}," 90",[75,1160,1161],{"class":140},", reps[",[75,1163,1151],{"class":85},[75,1165,214],{"class":140},[75,1167,630],{"class":136},[75,1169,1170],{"class":245}," 75",[75,1172,1161],{"class":140},[75,1174,1151],{"class":85},[75,1176,214],{"class":140},[75,1178,630],{"class":136},[75,1180,1181],{"class":245}," 50",[75,1183,717],{"class":140},[75,1185,1186,1189,1191,1193,1196,1198,1201,1203,1206],{"class":77,"line":165},[75,1187,1188],{"class":140},"grades ",[75,1190,171],{"class":136},[75,1192,701],{"class":140},[75,1194,1195],{"class":85},"\"A\"",[75,1197,222],{"class":140},[75,1199,1200],{"class":85},"\"B\"",[75,1202,222],{"class":140},[75,1204,1205],{"class":85},"\"C\"",[75,1207,717],{"class":140},[75,1209,1210,1212,1215,1217,1219,1222,1224,1226,1229],{"class":77,"line":183},[75,1211,752],{"class":140},[75,1213,1214],{"class":85},"\"IFS\"",[75,1216,214],{"class":140},[75,1218,171],{"class":136},[75,1220,1221],{"class":140}," np.select(conditions, grades, ",[75,1223,1082],{"class":453},[75,1225,171],{"class":136},[75,1227,1228],{"class":85},"\"Fail\"",[75,1230,180],{"class":140},[75,1232,1233,1235,1237,1239,1241,1243,1245,1247,1249,1251,1253,1255],{"class":77,"line":194},[75,1234,773],{"class":245},[75,1236,776],{"class":140},[75,1238,779],{"class":85},[75,1240,222],{"class":140},[75,1242,1151],{"class":85},[75,1244,222],{"class":140},[75,1246,1214],{"class":85},[75,1248,790],{"class":140},[75,1250,793],{"class":453},[75,1252,171],{"class":136},[75,1254,798],{"class":245},[75,1256,801],{"class":140},[10,1258,1259,1260,1262,1263,1266,1267,1269,1270,1273,1274,1277,1278,1280,1281,59],{},"Order matters exactly as in ",[14,1261,119],{},": a score of 93 satisfies all three conditions, and ",[14,1264,1265],{},"select"," takes the first, \"A\". Writing the conditions from the highest threshold down keeps them simple — each one only needs a lower bound because the rows above it have already been claimed. ",[14,1268,1082],{}," plays the role of the final ",[14,1271,1272],{},"TRUE, \"Fail\""," pair. Conditions can be any boolean expressions — combinations of columns, text tests, dates — which makes ",[14,1275,1276],{},"np.select"," the general replacement for nested ",[14,1279,40],{},"s; see also ",[55,1282,1284],{"href":1283},"\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",[971,1286,980,1291,980,1294,980,1297,980,1300,980,1307,980,1318,980,1323,980,1327,980,1331,980,1334,980,1339,980,1346,980,1351,980,1358,980,1365,980,1370,980,1374,980,1377,980,1380,980,1384,980,1387,980,1390,980,1395,980,1398,980,1401,980,1404,980,1407,980,1411,980,1414,980,1419,980,1425,980,1429,980,1432,980,1434,980,1437,980,1441,980,1444,980,1449,980,1456,980,1460],{"viewBox":1287,"role":974,"ariaLabelledBy":1288,"xmlns":978,"style":979},"4 0 752 272",[1289,1290],"csi-ifs-t","csi-ifs-d",[982,1292,1293],{"id":1289},"How IFS and numpy.select pick a grade",[986,1295,1296],{"id":1290},"A score is tested against 90 first; if at least 90 it is an A. Otherwise it is tested against 75 for a B, then against 50 for a C. A score that passes none of the tests gets the default, Fail.",[990,1298],{"x":587,"y":992,"width":993,"height":1299,"fill":995},"272",[990,1301],{"x":1302,"y":999,"width":1303,"height":1304,"rx":1305,"fill":1008,"stroke":1009,"style":1306},"240.0","280.0","75.5","12","stroke-width:2px",[1308,1309,1311],"g",{"transform":1310},"translate(368.0,29.0) scale(1.000)",[1312,1313],"path",{"d":1314,"fill":1315,"stroke":1316,"style":1317},"M3 5h18l-7 8v6l-4 2v-8z","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1012,1319,1322],{"x":1320,"y":1304,"style":1321},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Test conditions in order",[77,1324],{"x1":1320,"y1":1325,"x2":1320,"y2":1326,"stroke":1009,"style":1306},"91.5","111.5",[77,1328],{"x1":1329,"y1":1326,"x2":1330,"y2":1326,"stroke":1009,"style":1306},"104.0","656.0",[77,1332],{"x1":1329,"y1":1326,"x2":1329,"y2":1333,"stroke":1009,"style":1306},"157.5",[1335,1336],"polygon",{"points":1337,"fill":1338},"104.0,161.5 99.0,152.5 109.0,152.5","#5b5cf0",[990,1340],{"x":1341,"y":1342,"width":1343,"height":1344,"rx":1345,"fill":995,"stroke":1002,"style":1010},"63.4","121.5","81.3","21.0","10",[1012,1347,1350],{"x":1329,"y":1348,"style":1349},"136.0","font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","score ≥ 90",[990,1352],{"x":998,"y":1353,"width":1354,"height":1355,"rx":1305,"fill":1356,"stroke":1357,"style":1306},"163.5","168.0","93.0","#d9f4f1","var(--teal,#0f9488)",[1308,1359,1361],{"transform":1360},"translate(92.0,176.5) scale(1.000)",[1312,1362],{"d":1363,"fill":1315,"stroke":1364,"style":1317},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[1012,1366,1369],{"x":1329,"y":1367,"style":1368},"223.0","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","A",[1012,1371,1373],{"x":1329,"y":1372,"style":1048},"242.5","93, 90, 100",[77,1375],{"x1":1376,"y1":1326,"x2":1376,"y2":1333,"stroke":1009,"style":1306},"288.0",[1335,1378],{"points":1379,"fill":1338},"288.0,161.5 283.0,152.5 293.0,152.5",[990,1381],{"x":1382,"y":1342,"width":1383,"height":1344,"rx":1345,"fill":995,"stroke":1002,"style":1010},"248.1","79.8",[1012,1385,1386],{"x":1376,"y":1348,"style":1349},"score ≥ 75",[990,1388],{"x":1389,"y":1353,"width":1354,"height":1355,"rx":1305,"fill":1008,"stroke":1009,"style":1306},"204.0",[1308,1391,1393],{"transform":1392},"translate(276.0,176.5) scale(1.000)",[1312,1394],{"d":1363,"fill":1315,"stroke":1316,"style":1317},[1012,1396,1397],{"x":1376,"y":1367,"style":1321},"B",[1012,1399,1400],{"x":1376,"y":1372,"style":1048},"88, 75",[77,1402],{"x1":1403,"y1":1326,"x2":1403,"y2":1333,"stroke":1009,"style":1306},"472.0",[1335,1405],{"points":1406,"fill":1338},"472.0,161.5 467.0,152.5 477.0,152.5",[990,1408],{"x":1409,"y":1342,"width":1410,"height":1344,"rx":1345,"fill":995,"stroke":1002,"style":1010},"431.5","80.9",[1012,1412,1413],{"x":1403,"y":1348,"style":1349},"score ≥ 50",[990,1415],{"x":1416,"y":1353,"width":1354,"height":1355,"rx":1305,"fill":1417,"stroke":1418,"style":1306},"388.0","#fdefd8","var(--gold,#b4740a)",[1308,1420,1422],{"transform":1421},"translate(460.0,176.5) scale(1.000)",[1312,1423],{"d":1363,"fill":1315,"stroke":1424,"style":1317},"var(--gold-ink,#7a4e06)",[1012,1426,1428],{"x":1403,"y":1367,"style":1427},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","C",[1012,1430,1431],{"x":1403,"y":1372,"style":1048},"74, 61",[77,1433],{"x1":1330,"y1":1326,"x2":1330,"y2":1333,"stroke":1009,"style":1306},[1335,1435],{"points":1436,"fill":1338},"656.0,161.5 651.0,152.5 661.0,152.5",[990,1438],{"x":1439,"y":1342,"width":1440,"height":1344,"rx":1345,"fill":995,"stroke":1002,"style":1010},"618.7","74.6",[1012,1442,1443],{"x":1330,"y":1348,"style":1349},"none true",[990,1445],{"x":1446,"y":1353,"width":1354,"height":1355,"rx":1305,"fill":1447,"stroke":1448,"style":1306},"572.0","#fce9e9","var(--danger,#dc2626)",[1308,1450,1452],{"transform":1451},"translate(644.0,176.5) scale(1.000)",[1312,1453],{"d":1454,"fill":1315,"stroke":1455,"style":1317},"M6 6l12 12 M18 6L6 18","var(--accent-ink,#be185d)",[1012,1457,1459],{"x":1330,"y":1367,"style":1458},"font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Fail (default)",[1012,1461,1462],{"x":1330,"y":1372,"style":1048},"49",[61,1464,1466],{"id":1465},"step-5-bands-with-pdcut-and-the-cross-check","Step 5 — Bands with pd.cut, and the cross-check",[10,1468,1469,1470,1472],{},"When the conditions are simply consecutive numeric ranges, ",[14,1471,52],{}," expresses them as bin edges, which is shorter and harder to get wrong:",[66,1474,1476],{"className":127,"code":1475,"language":129,"meta":71,"style":71},"reps[\"cut\"] = pd.cut(reps[\"Score\"], bins=[-np.inf, 50, 75, 90, np.inf], labels=[\"Fail\", \"C\", \"B\", \"A\"], right=False)\n\nours = reps[[\"CHOOSE\", \"SWITCH\", \"IFS\"]].astype(object).where(reps[[\"CHOOSE\", \"SWITCH\", \"IFS\"]].notna(), None).values.tolist()\nfor name, i in ((\"CHOOSE\", 0), (\"SWITCH\", 1), (\"IFS\", 2)):\n    same = [(o[i] == e[i]) or (o[i] is None and isinstance(e[i], str) and e[i].startswith(\"#\")) for o, e in zip(ours, excel)]\n    print(f\"{name:6} matches spreadsheet on {sum(same)}\u002F{len(same)} rows\")\nprint(\"pd.cut agrees with IFS:\", (reps[\"cut\"].astype(str) == reps[\"IFS\"]).all())\n",[14,1477,1478,1558,1562,1610,1649,1716,1766],{"__ignoreMap":71},[75,1479,1480,1482,1485,1487,1489,1492,1494,1496,1499,1501,1504,1507,1510,1513,1515,1518,1520,1523,1526,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547,1549,1552,1554,1556],{"class":77,"line":78},[75,1481,752],{"class":140},[75,1483,1484],{"class":85},"\"cut\"",[75,1486,214],{"class":140},[75,1488,171],{"class":136},[75,1490,1491],{"class":140}," pd.cut(reps[",[75,1493,1151],{"class":85},[75,1495,450],{"class":140},[75,1497,1498],{"class":453},"bins",[75,1500,171],{"class":136},[75,1502,1503],{"class":140},"[",[75,1505,1506],{"class":136},"-",[75,1508,1509],{"class":140},"np.inf, ",[75,1511,1512],{"class":245},"50",[75,1514,222],{"class":140},[75,1516,1517],{"class":245},"75",[75,1519,222],{"class":140},[75,1521,1522],{"class":245},"90",[75,1524,1525],{"class":140},", np.inf], ",[75,1527,1528],{"class":453},"labels",[75,1530,171],{"class":136},[75,1532,1503],{"class":140},[75,1534,1228],{"class":85},[75,1536,222],{"class":140},[75,1538,1205],{"class":85},[75,1540,222],{"class":140},[75,1542,1200],{"class":85},[75,1544,222],{"class":140},[75,1546,1195],{"class":85},[75,1548,450],{"class":140},[75,1550,1551],{"class":453},"right",[75,1553,171],{"class":136},[75,1555,798],{"class":245},[75,1557,180],{"class":140},[75,1559,1560],{"class":77,"line":144},[75,1561,162],{"emptyLinePlaceholder":161},[75,1563,1564,1567,1569,1572,1574,1576,1578,1580,1582,1585,1588,1591,1593,1595,1597,1599,1601,1604,1607],{"class":77,"line":158},[75,1565,1566],{"class":140},"ours ",[75,1568,171],{"class":136},[75,1570,1571],{"class":140}," reps[[",[75,1573,755],{"class":85},[75,1575,222],{"class":140},[75,1577,225],{"class":85},[75,1579,222],{"class":140},[75,1581,1214],{"class":85},[75,1583,1584],{"class":140},"]].astype(",[75,1586,1587],{"class":245},"object",[75,1589,1590],{"class":140},").where(reps[[",[75,1592,755],{"class":85},[75,1594,222],{"class":140},[75,1596,225],{"class":85},[75,1598,222],{"class":140},[75,1600,1214],{"class":85},[75,1602,1603],{"class":140},"]].notna(), ",[75,1605,1606],{"class":245},"None",[75,1608,1609],{"class":140},").values.tolist()\n",[75,1611,1612,1614,1617,1619,1622,1624,1626,1628,1631,1633,1635,1638,1640,1642,1644,1646],{"class":77,"line":165},[75,1613,236],{"class":136},[75,1615,1616],{"class":140}," name, i ",[75,1618,242],{"class":136},[75,1620,1621],{"class":140}," ((",[75,1623,755],{"class":85},[75,1625,222],{"class":140},[75,1627,992],{"class":245},[75,1629,1630],{"class":140},"), (",[75,1632,225],{"class":85},[75,1634,222],{"class":140},[75,1636,1637],{"class":245},"1",[75,1639,1630],{"class":140},[75,1641,1214],{"class":85},[75,1643,222],{"class":140},[75,1645,252],{"class":245},[75,1647,1648],{"class":140},")):\n",[75,1650,1651,1654,1656,1659,1662,1665,1668,1671,1674,1677,1680,1683,1686,1689,1692,1695,1698,1701,1704,1706,1709,1711,1713],{"class":77,"line":183},[75,1652,1653],{"class":140},"    same ",[75,1655,171],{"class":136},[75,1657,1658],{"class":140}," [(o[i] ",[75,1660,1661],{"class":136},"==",[75,1663,1664],{"class":140}," e[i]) ",[75,1666,1667],{"class":136},"or",[75,1669,1670],{"class":140}," (o[i] ",[75,1672,1673],{"class":136},"is",[75,1675,1676],{"class":245}," None",[75,1678,1679],{"class":136}," and",[75,1681,1682],{"class":245}," isinstance",[75,1684,1685],{"class":140},"(e[i], ",[75,1687,1688],{"class":245},"str",[75,1690,1691],{"class":140},") ",[75,1693,1694],{"class":136},"and",[75,1696,1697],{"class":140}," e[i].startswith(",[75,1699,1700],{"class":85},"\"#\"",[75,1702,1703],{"class":140},")) ",[75,1705,236],{"class":136},[75,1707,1708],{"class":140}," o, e ",[75,1710,242],{"class":136},[75,1712,569],{"class":245},[75,1714,1715],{"class":140},"(ours, excel)]\n",[75,1717,1718,1720,1722,1724,1726,1728,1731,1734,1736,1739,1741,1744,1747,1749,1752,1754,1757,1759,1761,1764],{"class":77,"line":194},[75,1719,605],{"class":245},[75,1721,249],{"class":140},[75,1723,273],{"class":136},[75,1725,289],{"class":85},[75,1727,280],{"class":279},[75,1729,1730],{"class":140},"name",[75,1732,1733],{"class":136},":6",[75,1735,286],{"class":279},[75,1737,1738],{"class":85}," matches spreadsheet on ",[75,1740,280],{"class":279},[75,1742,1743],{"class":245},"sum",[75,1745,1746],{"class":140},"(same)",[75,1748,286],{"class":279},[75,1750,1751],{"class":85},"\u002F",[75,1753,280],{"class":279},[75,1755,1756],{"class":245},"len",[75,1758,1746],{"class":140},[75,1760,286],{"class":279},[75,1762,1763],{"class":85}," rows\"",[75,1765,180],{"class":140},[75,1767,1768,1770,1772,1775,1778,1780,1783,1785,1787,1789,1791,1793],{"class":77,"line":233},[75,1769,773],{"class":245},[75,1771,249],{"class":140},[75,1773,1774],{"class":85},"\"pd.cut agrees with IFS:\"",[75,1776,1777],{"class":140},", (reps[",[75,1779,1484],{"class":85},[75,1781,1782],{"class":140},"].astype(",[75,1784,1688],{"class":245},[75,1786,1691],{"class":140},[75,1788,1661],{"class":136},[75,1790,762],{"class":140},[75,1792,1214],{"class":85},[75,1794,1795],{"class":140},"]).all())\n",[10,1797,1798,1801,1802,1805,1806,1808,1809,1811,1812,1814,1815,1817,1818,59],{},[14,1799,1800],{},"right=False"," makes each bin include its left edge — ",[14,1803,1804],{},"[75, 90)"," — so a score of exactly 75 is a B and exactly 90 an A, matching the ",[14,1807,630],{}," tests. The comparison confirms all three translations agree with the spreadsheet on every row, treating Grace's ",[14,1810,620],{}," error as equivalent to a missing value, and that ",[14,1813,52],{}," gives the same grades as ",[14,1816,119],{},". Banding in general is covered in ",[55,1819,1821],{"href":1820},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut\u002F","Bin Values into Ranges with pandas cut",[61,1823,1825],{"id":1824},"performance","Performance",[10,1827,1828,1829,1832,1833,1751,1835,1838],{},"All of these are vectorised: they work on whole columns at once. The row-by-row alternative — ",[14,1830,1831],{},"df.apply(...)"," with an ",[14,1834,48],{},[14,1836,1837],{},"elif"," chain inside — gives the same answers but calls Python once per row. Timing both on a larger table makes the difference concrete:",[66,1840,1842],{"className":127,"code":1841,"language":129,"meta":71,"style":71},"import time\n\nbig = pd.DataFrame({\"Score\": np.random.default_rng(0).integers(0, 101, 200_000)})\n\ndef grade(s):\n    if s >= 90:\n        return \"A\"\n    elif s >= 75:\n        return \"B\"\n    elif s >= 50:\n        return \"C\"\n    return \"Fail\"\n\nt0 = time.perf_counter(); slow = big[\"Score\"].apply(grade); t_apply = time.perf_counter() - t0\nt0 = time.perf_counter()\nfast = np.select([big[\"Score\"] >= 90, big[\"Score\"] >= 75, big[\"Score\"] >= 50], [\"A\", \"B\", \"C\"], default=\"Fail\")\nt_select = time.perf_counter() - t0\nprint(f\"apply: {t_apply:.3f}s | np.select: {t_select:.3f}s | same result: {(slow.values == fast).all()}\")\n",[14,1843,1844,1851,1855,1890,1894,1906,1921,1929,1942,1949,1961,1968,1976,1980,2010,2019,2081,2095],{"__ignoreMap":71},[75,1845,1846,1848],{"class":77,"line":78},[75,1847,137],{"class":136},[75,1849,1850],{"class":140}," time\n",[75,1852,1853],{"class":77,"line":144},[75,1854,162],{"emptyLinePlaceholder":161},[75,1856,1857,1860,1862,1865,1867,1870,1872,1875,1877,1879,1882,1884,1887],{"class":77,"line":158},[75,1858,1859],{"class":140},"big ",[75,1861,171],{"class":136},[75,1863,1864],{"class":140}," pd.DataFrame({",[75,1866,1151],{"class":85},[75,1868,1869],{"class":140},": np.random.default_rng(",[75,1871,992],{"class":245},[75,1873,1874],{"class":140},").integers(",[75,1876,992],{"class":245},[75,1878,222],{"class":140},[75,1880,1881],{"class":245},"101",[75,1883,222],{"class":140},[75,1885,1886],{"class":245},"200_000",[75,1888,1889],{"class":140},")})\n",[75,1891,1892],{"class":77,"line":165},[75,1893,162],{"emptyLinePlaceholder":161},[75,1895,1896,1899,1903],{"class":77,"line":183},[75,1897,1898],{"class":136},"def",[75,1900,1902],{"class":1901},"s_Opv"," grade",[75,1904,1905],{"class":140},"(s):\n",[75,1907,1908,1911,1914,1916,1918],{"class":77,"line":194},[75,1909,1910],{"class":136},"    if",[75,1912,1913],{"class":140}," s ",[75,1915,630],{"class":136},[75,1917,1158],{"class":245},[75,1919,1920],{"class":140},":\n",[75,1922,1923,1926],{"class":77,"line":233},[75,1924,1925],{"class":136},"        return",[75,1927,1928],{"class":85}," \"A\"\n",[75,1930,1931,1934,1936,1938,1940],{"class":77,"line":267},[75,1932,1933],{"class":136},"    elif",[75,1935,1913],{"class":140},[75,1937,630],{"class":136},[75,1939,1170],{"class":245},[75,1941,1920],{"class":140},[75,1943,1944,1946],{"class":77,"line":311},[75,1945,1925],{"class":136},[75,1947,1948],{"class":85}," \"B\"\n",[75,1950,1951,1953,1955,1957,1959],{"class":77,"line":347},[75,1952,1933],{"class":136},[75,1954,1913],{"class":140},[75,1956,630],{"class":136},[75,1958,1181],{"class":245},[75,1960,1920],{"class":140},[75,1962,1963,1965],{"class":77,"line":401},[75,1964,1925],{"class":136},[75,1966,1967],{"class":85}," \"C\"\n",[75,1969,1970,1973],{"class":77,"line":412},[75,1971,1972],{"class":136},"    return",[75,1974,1975],{"class":85}," \"Fail\"\n",[75,1977,1978],{"class":77,"line":473},[75,1979,162],{"emptyLinePlaceholder":161},[75,1981,1982,1985,1987,1990,1992,1995,1997,2000,2002,2005,2007],{"class":77,"line":498},[75,1983,1984],{"class":140},"t0 ",[75,1986,171],{"class":136},[75,1988,1989],{"class":140}," time.perf_counter(); slow ",[75,1991,171],{"class":136},[75,1993,1994],{"class":140}," big[",[75,1996,1151],{"class":85},[75,1998,1999],{"class":140},"].apply(grade); t_apply ",[75,2001,171],{"class":136},[75,2003,2004],{"class":140}," time.perf_counter() ",[75,2006,1506],{"class":136},[75,2008,2009],{"class":140}," t0\n",[75,2011,2012,2014,2016],{"class":77,"line":559},[75,2013,1984],{"class":140},[75,2015,171],{"class":136},[75,2017,2018],{"class":140}," time.perf_counter()\n",[75,2020,2021,2024,2026,2029,2031,2033,2035,2037,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2061,2063,2065,2067,2069,2071,2073,2075,2077,2079],{"class":77,"line":602},[75,2022,2023],{"class":140},"fast ",[75,2025,171],{"class":136},[75,2027,2028],{"class":140}," np.select([big[",[75,2030,1151],{"class":85},[75,2032,214],{"class":140},[75,2034,630],{"class":136},[75,2036,1158],{"class":245},[75,2038,2039],{"class":140},", big[",[75,2041,1151],{"class":85},[75,2043,214],{"class":140},[75,2045,630],{"class":136},[75,2047,1170],{"class":245},[75,2049,2039],{"class":140},[75,2051,1151],{"class":85},[75,2053,214],{"class":140},[75,2055,630],{"class":136},[75,2057,1181],{"class":245},[75,2059,2060],{"class":140},"], [",[75,2062,1195],{"class":85},[75,2064,222],{"class":140},[75,2066,1200],{"class":85},[75,2068,222],{"class":140},[75,2070,1205],{"class":85},[75,2072,450],{"class":140},[75,2074,1082],{"class":453},[75,2076,171],{"class":136},[75,2078,1228],{"class":85},[75,2080,180],{"class":140},[75,2082,2084,2087,2089,2091,2093],{"class":77,"line":2083},17,[75,2085,2086],{"class":140},"t_select ",[75,2088,171],{"class":136},[75,2090,2004],{"class":140},[75,2092,1506],{"class":136},[75,2094,2009],{"class":140},[75,2096,2098,2100,2102,2104,2107,2109,2112,2115,2117,2120,2122,2125,2127,2129,2132,2134,2137,2139,2142,2144,2146],{"class":77,"line":2097},18,[75,2099,773],{"class":245},[75,2101,249],{"class":140},[75,2103,273],{"class":136},[75,2105,2106],{"class":85},"\"apply: ",[75,2108,280],{"class":279},[75,2110,2111],{"class":140},"t_apply",[75,2113,2114],{"class":136},":.3f",[75,2116,286],{"class":279},[75,2118,2119],{"class":85},"s | np.select: ",[75,2121,280],{"class":279},[75,2123,2124],{"class":140},"t_select",[75,2126,2114],{"class":136},[75,2128,286],{"class":279},[75,2130,2131],{"class":85},"s | same result: ",[75,2133,280],{"class":279},[75,2135,2136],{"class":140},"(slow.values ",[75,2138,1661],{"class":136},[75,2140,2141],{"class":140}," fast).all()",[75,2143,286],{"class":279},[75,2145,289],{"class":85},[75,2147,180],{"class":140},[10,2149,2150,2151,2153,2154,2157,2158,2160],{},"On 200,000 rows the vectorised version was about ten times faster in this run — and that is the favourable case for ",[14,2152,44],{},", a single column; a row-wise ",[14,2155,2156],{},"df.apply(..., axis=1)"," over several columns is far slower still. The vectorised code is also easier to test, because the rules are visible as a list of conditions rather than buried in a function. Reach for ",[14,2159,44],{}," only when a rule genuinely cannot be expressed as column operations.",[971,2162,980,2167,980,2170,980,2173,980,2176,980,2181,980,2188,980,2193,980,2198,980,2202,980,2206,980,2210,980,2213,980,2219,980,2224,980,2228,980,2231,980,2234],{"viewBox":2163,"role":974,"ariaLabelledBy":2164,"xmlns":978,"style":979},"4 0 752 207",[2165,2166],"csi-speed-t","csi-speed-d",[982,2168,2169],{"id":2165},"Row-by-row apply versus vectorised choice",[986,2171,2172],{"id":2166},"An if-elif chain inside apply calls Python once per row, is slower on large tables and hides the rules in a function. numpy.select, map and pandas.cut work on whole columns, run much faster and keep the rules visible as lists.",[990,2174],{"x":587,"y":992,"width":993,"height":2175,"fill":995},"207",[990,2177],{"x":998,"y":999,"width":2178,"height":2179,"rx":2180,"fill":1417,"stroke":1418,"style":1306},"351.0","175.0","14",[1308,2182,2184],{"transform":2183},"translate(182.5,29.0) scale(1.083)",[1312,2185],{"d":2186,"fill":1315,"stroke":1424,"style":2187},"M12 3a9 9 0 1 0 .01 0z M12 7v5l3.5 2","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[1012,2189,2192],{"x":2190,"y":1040,"style":2191},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","apply + if\u002Felif",[77,2194],{"x1":2195,"y1":2196,"x2":2197,"y2":2196,"stroke":1418,"style":1003},"36.0","91.0","355.0",[1012,2199,2201],{"x":2190,"y":1071,"style":2200},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","one Python call per row",[1012,2203,2205],{"x":2190,"y":2204,"style":2200},"137.0","slower as rows grow",[1012,2207,2209],{"x":2190,"y":2208,"style":2200},"163.0","rules hidden in a function",[990,2211],{"x":2212,"y":999,"width":2178,"height":2179,"rx":2180,"fill":1356,"stroke":1357,"style":1306},"389.0",[1308,2214,2216],{"transform":2215},"translate(551.5,29.0) scale(1.083)",[1312,2217],{"d":2218,"fill":1315,"stroke":1364,"style":2187},"M13 2L4 14h7l-1 8 9-12h-7z",[1012,2220,2223],{"x":2221,"y":1040,"style":2222},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","np.select \u002F map \u002F pd.cut",[77,2225],{"x1":2226,"y1":2196,"x2":2227,"y2":2196,"stroke":1357,"style":1003},"405.0","724.0",[1012,2229,2230],{"x":2221,"y":1071,"style":2200},"whole columns at once",[1012,2232,2233],{"x":2221,"y":2204,"style":2200},"much faster on large data",[1012,2235,2236],{"x":2221,"y":2208,"style":2200},"rules visible as lists",[61,2238,2240],{"id":2239},"common-pitfalls","Common pitfalls",[2242,2243,2244,2255,2273,2283,2295],"ul",{},[2245,2246,2247,2251,2252,2254],"li",{},[2248,2249,2250],"strong",{},"0-based versus 1-based positions."," Excel's ",[14,2253,620],{}," counts from 1; build the mapping from 1.",[2245,2256,2257,2260,2261,2263,2264,2266,2267,2269,2270,2272],{},[2248,2258,2259],{},"Forgetting the default."," ",[14,2262,831],{}," leaves unmatched values as ",[14,2265,810],{},"; add ",[14,2268,835],{}," if ",[14,2271,115],{}," had a default.",[2245,2274,2275,2260,2278,2280,2281,59],{},[2248,2276,2277],{},"Conditions in the wrong order.",[14,2279,1276],{}," takes the first true condition, like ",[14,2282,119],{},[2245,2284,2285,2288,2289,2291,2292,2294],{},[2248,2286,2287],{},"Boundary values."," Decide whether 75 is a B or a C, and use ",[14,2290,630],{}," or ",[14,2293,1800],{}," consistently.",[2245,2296,2297,2302,2303,222,2305,2291,2307,59],{},[2248,2298,2299,2301],{},[14,2300,44],{}," with if-chains."," Slow and hard to test; use ",[14,2304,831],{},[14,2306,1276],{},[14,2308,52],{},[61,2310,2312],{"id":2311},"conclusion","Conclusion",[10,2314,2315,2316,2318,2319,2321,2322,2324,2325,2327,2328,2330,2331,2333,2334,2336,2337,2339],{},"Excel's three multi-way choice functions map onto three vectorised pandas tools. ",[14,2317,620],{}," by position becomes ",[14,2320,831],{}," with a dictionary keyed from 1, ",[14,2323,115],{}," by value becomes ",[14,2326,831],{}," with a dictionary plus ",[14,2329,835],{}," for the default, and ",[14,2332,119],{}," with ordered conditions becomes ",[14,2335,1116],{}," with a default — or ",[14,2338,52],{}," when the conditions are consecutive ranges. Decide deliberately what an unmatched value should become, keep boundaries consistent with the spreadsheet, and confirm the translation against the original formulas.",[61,2341,2343],{"id":2342},"frequently-asked-questions","Frequently asked questions",[10,2345,2346,2349],{},[2248,2347,2348],{},"What is the pandas equivalent of Excel's IFS?","\nnumpy.select, which takes a list of boolean conditions, a list of results and a default, and returns the result of the first true condition for each row.",[10,2351,2352,2355],{},[2248,2353,2354],{},"How do I replicate SWITCH in pandas?","\nUse Series.map with a dictionary of values to results, then fillna with the default result for values not in the dictionary.",[10,2357,2358,2361],{},[2248,2359,2360],{},"How do I replicate CHOOSE in pandas?","\nMap the position column with a dictionary from 1-based positions to values. Positions outside the list become NaN, where Excel would show an error.",[10,2363,2364,2367],{},[2248,2365,2366],{},"Should I use apply with if statements instead?","\nIt works but runs row by row and is much slower on large data. map, numpy.select and pandas.cut are vectorised.",[61,2369,2371],{"id":2370},"related","Related",[2242,2373,2374,2380,2387,2394],{},[2245,2375,2376,2377,2379],{},"Up one level: ",[55,2378,58],{"href":57}," — spreadsheet functions in Python.",[2245,2381,2382,2386],{},[55,2383,2385],{"href":2384},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fiferror-and-isna-equivalents-in-pandas\u002F","IFERROR and ISNA Equivalents in pandas"," — handling the no-match cases.",[2245,2388,2389,2393],{},[55,2390,2392],{"href":2391},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fxlookup-equivalent-in-pandas\u002F","XLOOKUP Equivalent in pandas"," — choosing by lookup table.",[2245,2395,2396,2400],{},[55,2397,2399],{"href":2398},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Freplace-values-in-an-excel-column-with-pandas\u002F","Replace Values in an Excel Column with pandas"," — map versus replace.",[2402,2403,2404],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":71,"searchDepth":144,"depth":144,"links":2406},[2407,2408,2409,2410,2411,2412,2413,2414,2415,2416,2417],{"id":63,"depth":144,"text":64},{"id":108,"depth":144,"text":109},{"id":649,"depth":144,"text":650},{"id":822,"depth":144,"text":823},{"id":1107,"depth":144,"text":1108},{"id":1465,"depth":144,"text":1466},{"id":1824,"depth":144,"text":1825},{"id":2239,"depth":144,"text":2240},{"id":2311,"depth":144,"text":2312},{"id":2342,"depth":144,"text":2343},{"id":2370,"depth":144,"text":2371},"2026-10-02","Translate Excel's multi-way choice formulas into vectorised pandas: CHOOSE by position with a lookup list, SWITCH by value with Series.map and a default, IFS with ordered conditions using numpy.select, ranges with pandas.cut, what happens when nothing matches, and a check that each version gives the same answers as the spreadsheet formulas.","md",[2422,2424,2426,2428],{"q":2348,"a":2423},"numpy.select, which takes a list of boolean conditions, a list of results and a default, and returns the result of the first true condition for each row.",{"q":2354,"a":2425},"Use Series.map with a dictionary of values to results, then fillna with the default result for values not in the dictionary.",{"q":2360,"a":2427},"Map the position column with a dictionary from 1-based positions to values. Positions outside the list become NaN, where Excel would show an error.",{"q":2366,"a":2429},"It works but runs row by row and is much slower on large data. map, numpy.select and pandas.cut are vectorised.","choose switch and ifs equivalents in pandas",null,{"breadcrumb":2433},[2434,2436,2439],{"name":2435,"item":1751},"Home",{"name":2437,"item":2438},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":58,"item":57},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas",{"title":2442,"description":2443},"CHOOSE, SWITCH and IFS Equivalents in pandas (numpy.select)","Replace Excel CHOOSE, SWITCH and IFS with pandas: positional lookup lists, Series.map with defaults, numpy.select for ordered conditions, pandas.cut for bands, no-match behaviour and spreadsheet-verified results.","choose-switch-and-ifs-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fchoose-switch-and-ifs-equivalents-in-pandas\u002Findex",[2447],"formula-equivalents","how-to","nc8K0p2nntiYOgY9IvdQkEII5iKMGjvCyfDU4qi-kP0",[2451,2455],{"title":2452,"path":2453,"stem":2454,"children":-1},"AVERAGEIFS, MAXIFS and MINIFS Equivalents in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Faverageifs-maxifs-minifs-in-pandas\u002Findex",{"title":2456,"path":2457,"stem":2458,"children":-1},"CORREL and LINEST Equivalents in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcorrel-and-linest-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcorrel-and-linest-equivalents-in-python\u002Findex",[2460,2462],{"path":2461,"title":58},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2463,"title":2437},"\u002Fadvanced-data-transformation-and-cleaning",[],[2466,2469,2472,2475,2478,2481],{"path":2467,"title":2468,"kind":2448},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fvlookup-equivalent-in-pandas-for-excel-files","The VLOOKUP Equivalent in pandas for Excel Files",{"path":2470,"title":2471,"kind":2448},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas","Extract Numbers and Codes from Text with pandas",{"path":2473,"title":2474,"kind":2448},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-full-names-into-first-and-last-names-with-pandas","Split Full Names into First and Last Names with pandas",{"path":2476,"title":2477,"kind":2448},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut","Bin Values into Ranges Like Excel FREQUENCY with pd.cut",{"path":2479,"title":2480,"kind":2448},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcalculate-percent-of-total-per-group-in-pandas","Calculate Percent of Total per Group in pandas",{"path":2482,"title":2483,"kind":2448},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fcount-distinct-values-per-group-in-pandas","Count Distinct Values per Group in pandas",1790972086806]