[{"data":1,"prerenderedAt":2036},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas":2002,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas":2011,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas":2016,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas":2017},{"id":4,"title":5,"body":6,"dateModified":1969,"datePublished":1969,"description":1970,"extension":1971,"faq":1972,"keyword":1981,"kind":1982,"meta":1983,"navigation":448,"path":1992,"seo":1993,"slug":1995,"stem":1996,"topics":1997,"type":2000,"__hash__":2001},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas\u002Findex.md","EXACT and Case-Sensitive Comparisons in pandas",{"type":7,"value":8,"toc":1955},"minimark",[9,77,82,110,120,124,172,227,418,422,533,548,552,792,830,906,910,940,1224,1259,1376,1380,1661,1683,1687,1710,1756,1769,1773,1776,1786,1790,1838,1842,1867,1871,1881,1894,1903,1918,1922,1951],[10,11,12,13,17,18,21,22,26,27,30,31,30,34,30,37,17,40,43,44,47,48,30,51,30,54,30,57,17,60,63,64,67,68,70,71,76],"p",{},"A report moved from Excel to pandas counts six different product codes where the spreadsheet always showed three. Nothing is broken; the two tools simply disagree about whether ",[14,15,16],"em",{},"AB-100"," and ",[14,19,20],{},"ab-100"," are the same text. Excel's comparisons are case-insensitive: the ",[23,24,25],"code",{},"="," operator, ",[23,28,29],{},"COUNTIF",", ",[23,32,33],{},"MATCH",[23,35,36],{},"XLOOKUP",[23,38,39],{},"VLOOKUP",[23,41,42],{},"UNIQUE"," all treat upper and lower case as equal, and only the ",[23,45,46],{},"EXACT"," function tells them apart. pandas does the opposite: ",[23,49,50],{},"==",[23,52,53],{},"value_counts",[23,55,56],{},"nunique",[23,58,59],{},"merge",[23,61,62],{},"drop_duplicates"," all compare exactly, character for character. Neither is wrong, but a migration has to decide which behaviour the report needs and reproduce it on purpose. This page shows Excel's results for a small set of codes, then rebuilds each one in pandas — the case-insensitive ones with ",[23,65,66],{},"casefold"," and the case-sensitive ",[23,69,46],{}," with plain equality. It belongs to ",[72,73,75],"a",{"href":74},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[78,79,81],"h2",{"id":80},"prerequisites","Prerequisites",[83,84,89],"pre",{"className":85,"code":86,"language":87,"meta":88,"style":88},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[23,90,91],{"__ignoreMap":88},[92,93,96,100,104,107],"span",{"class":94,"line":95},"line",1,[92,97,99],{"class":98},"sMTad","pip",[92,101,103],{"class":102},"srMev"," install",[92,105,106],{"class":102}," pandas",[92,108,109],{"class":102}," openpyxl\n",[10,111,112,113,115,116,119],{},"The sample is six product codes as they might arrive from different systems: ",[14,114,16],{}," in three spellings, one with a trailing space, and ",[14,117,118],{},"CD-200"," in two. Beside them are eight Excel formulas that compare and count them. LibreOffice calculates the formulas so the pandas results can be checked against real spreadsheet behaviour.",[78,121,123],{"id":122},"step-1-what-excel-says","Step 1 — What Excel says",[83,125,127],{"className":85,"code":126,"language":87,"meta":88,"style":88},"soffice --headless --convert-to 'csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,true' codes.xlsx > \u002Fdev\u002Fnull 2>&1\ncut -d, -f3- codes.csv\n",[23,128,129,157],{"__ignoreMap":88},[92,130,131,134,138,141,144,147,151,154],{"class":94,"line":95},[92,132,133],{"class":98},"soffice",[92,135,137],{"class":136},"sP0c6"," --headless",[92,139,140],{"class":136}," --convert-to",[92,142,143],{"class":102}," 'csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,true'",[92,145,146],{"class":102}," codes.xlsx",[92,148,150],{"class":149},"s-kum"," >",[92,152,153],{"class":102}," \u002Fdev\u002Fnull",[92,155,156],{"class":149}," 2>&1\n",[92,158,160,163,166,169],{"class":94,"line":159},2,[92,161,162],{"class":98},"cut",[92,164,165],{"class":136}," -d,",[92,167,168],{"class":136}," -f3-",[92,170,171],{"class":102}," codes.csv\n",[10,173,174,175,178,179,182,183,185,186,189,190,192,193,195,196,30,198,17,200,203,204,207,208,17,210,192,212,214,215,217,218,192,220,223,224,226],{},"The results show Excel's rules plainly. ",[23,176,177],{},"\"AB-100\"=\"ab-100\""," is ",[23,180,181],{},"TRUE",", while ",[23,184,46],{}," on the same pair is ",[23,187,188],{},"FALSE",". ",[23,191,29],{}," for ",[14,194,16],{}," finds three cells — ",[14,197,16],{},[14,199,20],{},[14,201,202],{},"Ab-100"," — but not *AB-100 * with its trailing space, because spaces are significant even when case is not. Counting case-sensitively needs ",[23,205,206],{},"SUMPRODUCT(--EXACT(...))",", which finds one. ",[23,209,33],{},[23,211,36],{},[14,213,20],{}," return the first cell, ",[14,216,16],{},", because it matches ignoring case. The wildcard ",[23,219,29],{},[23,221,222],{},"ab*"," finds four, including the one with the trailing space, since the wildcard absorbs it. And ",[23,225,42],{}," sees three distinct values.",[228,229,237,238,237,242,237,246,237,254,237,263,237,267,237,274,237,278,237,282,237,286,237,291,237,296,237,300,237,302,237,305,237,308,237,312,237,314,237,318,237,320,237,322,237,325,237,329,237,331,237,333,237,337,237,340,237,343,237,347,237,349,237,351,237,353,237,355,237,358,237,362,237,364,237,367,237,369,237,371,237,374,237,378,237,380,237,382,237,384,237,386,237,389,237,393,237,395,237,398,237,400,237,402,237,405,237,409,237,411,237,414,237,416],"svg",{"viewBox":230,"role":231,"ariaLabelledBy":232,"xmlns":235,"style":236},"234 0 291 280","img",[233,234],"ex-excel-t","ex-excel-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:291px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[239,240,241],"title",{"id":233},"Excel's answers for the six codes",[243,244,245],"desc",{"id":234},"Equality ignores case and returns TRUE, EXACT returns FALSE. COUNTIF for AB-100 finds three cells, the case-sensitive SUMPRODUCT EXACT count finds one. MATCH returns position 1, the wildcard count finds four, and UNIQUE finds three distinct values.",[247,248],"rect",{"x":249,"y":250,"width":251,"height":252,"fill":253},"234","0","291","280","#ffffff",[247,255],{"x":256,"y":257,"width":258,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"250.8","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[247,264],{"x":265,"y":257,"width":266,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"282.8","162.5",[268,269,273],"text",{"x":270,"y":271,"style":272},"364.1","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[247,275],{"x":276,"y":257,"width":277,"height":259,"rx":250,"fill":260,"stroke":261,"style":262},"445.3","63.8",[268,279,281],{"x":280,"y":271,"style":272},"477.2","B",[247,283],{"x":256,"y":284,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"40.0","28.0",[268,287,290],{"x":288,"y":289,"style":272},"266.8","59.0","1",[247,292],{"x":265,"y":284,"width":266,"height":285,"rx":250,"fill":293,"stroke":294,"style":295},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[268,297,299],{"x":270,"y":289,"style":298},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Formula",[247,301],{"x":276,"y":284,"width":277,"height":285,"rx":250,"fill":293,"stroke":294,"style":295},[268,303,304],{"x":280,"y":289,"style":298},"Result",[247,306],{"x":256,"y":307,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"68.0",[268,309,311],{"x":288,"y":310,"style":272},"87.0","2",[247,313],{"x":265,"y":307,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,315,317],{"x":270,"y":310,"style":316},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","= (equals)",[247,319],{"x":276,"y":307,"width":277,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,321,181],{"x":280,"y":310,"style":316},[247,323],{"x":256,"y":324,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"96.0",[268,326,328],{"x":288,"y":327,"style":272},"115.0","3",[247,330],{"x":265,"y":324,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,332,46],{"x":270,"y":327,"style":316},[247,334],{"x":276,"y":324,"width":277,"height":285,"rx":250,"fill":335,"stroke":336,"style":295},"#fdefd8","var(--gold,#b4740a)",[268,338,188],{"x":280,"y":327,"style":339},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[247,341],{"x":256,"y":342,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"124.0",[268,344,346],{"x":288,"y":345,"style":272},"143.0","4",[247,348],{"x":265,"y":342,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,350,29],{"x":270,"y":345,"style":316},[247,352],{"x":276,"y":342,"width":277,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,354,328],{"x":280,"y":345,"style":316},[247,356],{"x":256,"y":357,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"152.0",[268,359,361],{"x":288,"y":360,"style":272},"171.0","5",[247,363],{"x":265,"y":357,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,365,366],{"x":270,"y":360,"style":316},"SUMPRODUCT EXACT",[247,368],{"x":276,"y":357,"width":277,"height":285,"rx":250,"fill":335,"stroke":336,"style":295},[268,370,290],{"x":280,"y":360,"style":339},[247,372],{"x":256,"y":373,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"180.0",[268,375,377],{"x":288,"y":376,"style":272},"199.0","6",[247,379],{"x":265,"y":373,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,381,33],{"x":270,"y":376,"style":316},[247,383],{"x":276,"y":373,"width":277,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,385,290],{"x":280,"y":376,"style":316},[247,387],{"x":256,"y":388,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"208.0",[268,390,392],{"x":288,"y":391,"style":272},"227.0","7",[247,394],{"x":265,"y":388,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,396,397],{"x":270,"y":391,"style":316},"COUNTIF ab*",[247,399],{"x":276,"y":388,"width":277,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,401,346],{"x":280,"y":391,"style":316},[247,403],{"x":256,"y":404,"width":258,"height":285,"rx":250,"fill":260,"stroke":261,"style":262},"236.0",[268,406,408],{"x":288,"y":407,"style":272},"255.0","8",[247,410],{"x":265,"y":404,"width":266,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,412,413],{"x":270,"y":407,"style":316},"ROWS(UNIQUE)",[247,415],{"x":276,"y":404,"width":277,"height":285,"rx":250,"fill":253,"stroke":261,"style":262},[268,417,328],{"x":280,"y":407,"style":316},[78,419,421],{"id":420},"step-2-pandas-compares-exactly-by-default","Step 2 — pandas compares exactly by default",[83,423,427],{"className":424,"code":425,"language":426,"meta":88,"style":88},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ncodes = pd.read_excel(\"codes.xlsx\", usecols=\"A\")[\"Code\"]\nprint(\"== 'AB-100':\", int((codes == \"AB-100\").sum()))\nprint(\"nunique():\", codes.nunique())\nprint(codes.value_counts().to_dict())\n","python",[23,428,429,444,450,484,512,525],{"__ignoreMap":88},[92,430,431,434,438,441],{"class":94,"line":95},[92,432,433],{"class":149},"import",[92,435,437],{"class":436},"skGVy"," pandas ",[92,439,440],{"class":149},"as",[92,442,443],{"class":436}," pd\n",[92,445,446],{"class":94,"line":159},[92,447,449],{"emptyLinePlaceholder":448},true,"\n",[92,451,453,456,458,461,464,466,470,472,475,478,481],{"class":94,"line":452},3,[92,454,455],{"class":436},"codes ",[92,457,25],{"class":149},[92,459,460],{"class":436}," pd.read_excel(",[92,462,463],{"class":102},"\"codes.xlsx\"",[92,465,30],{"class":436},[92,467,469],{"class":468},"sa561","usecols",[92,471,25],{"class":149},[92,473,474],{"class":102},"\"A\"",[92,476,477],{"class":436},")[",[92,479,480],{"class":102},"\"Code\"",[92,482,483],{"class":436},"]\n",[92,485,487,490,493,496,498,501,504,506,509],{"class":94,"line":486},4,[92,488,489],{"class":136},"print",[92,491,492],{"class":436},"(",[92,494,495],{"class":102},"\"== 'AB-100':\"",[92,497,30],{"class":436},[92,499,500],{"class":136},"int",[92,502,503],{"class":436},"((codes ",[92,505,50],{"class":149},[92,507,508],{"class":102}," \"AB-100\"",[92,510,511],{"class":436},").sum()))\n",[92,513,515,517,519,522],{"class":94,"line":514},5,[92,516,489],{"class":136},[92,518,492],{"class":436},[92,520,521],{"class":102},"\"nunique():\"",[92,523,524],{"class":436},", codes.nunique())\n",[92,526,528,530],{"class":94,"line":527},6,[92,529,489],{"class":136},[92,531,532],{"class":436},"(codes.value_counts().to_dict())\n",[10,534,535,536,538,539,541,542,544,545,547],{},"Read into pandas, the same six cells give different answers. Equality with ",[14,537,16],{}," matches one cell, not three; ",[23,540,56],{}," reports six distinct codes, not three; and ",[23,543,53],{}," lists each spelling separately. That is pandas behaving like ",[23,546,46],{}," everywhere. For a column of identifiers where case carries meaning — some part numbers and most passwords and hashes do — that is right. For codes typed by people into different systems, it is usually not what the report intends, and the old spreadsheet's totals were relying on Excel's case-insensitivity without anyone noticing.",[78,549,551],{"id":550},"step-3-excels-case-insensitive-rules-in-pandas","Step 3 — Excel's case-insensitive rules in pandas",[83,553,555],{"className":424,"code":554,"language":426,"meta":88,"style":88},"key = codes.str.casefold()\ntarget = \"ab-100\".casefold()\n\nresults = {\n    '\"AB-100\"=\"ab-100\"': \"AB-100\".casefold() == \"ab-100\".casefold(),\n    'EXACT(\"AB-100\",\"ab-100\")': \"AB-100\" == \"ab-100\",\n    'COUNTIF(...,\"AB-100\")': int((key == target).sum()),\n    \"SUMPRODUCT(--EXACT(...))\": int((codes == \"AB-100\").sum()),\n    'MATCH(\"ab-100\",...,0)': int(key.eq(target).to_numpy().argmax()) + 1,\n    'XLOOKUP(\"ab-100\",...)': codes[key.eq(target)].iloc[0],\n    'COUNTIF(...,\"ab*\")': int(key.str.startswith(\"ab\").sum()),\n    \"ROWS(UNIQUE(...))\": key.nunique(),\n}\nfor formula, value in results.items():\n    print(f\"{formula:28} {value}\")\n",[23,556,557,567,580,584,594,615,632,650,669,690,704,722,731,737,752],{"__ignoreMap":88},[92,558,559,562,564],{"class":94,"line":95},[92,560,561],{"class":436},"key ",[92,563,25],{"class":149},[92,565,566],{"class":436}," codes.str.casefold()\n",[92,568,569,572,574,577],{"class":94,"line":159},[92,570,571],{"class":436},"target ",[92,573,25],{"class":149},[92,575,576],{"class":102}," \"ab-100\"",[92,578,579],{"class":436},".casefold()\n",[92,581,582],{"class":94,"line":452},[92,583,449],{"emptyLinePlaceholder":448},[92,585,586,589,591],{"class":94,"line":486},[92,587,588],{"class":436},"results ",[92,590,25],{"class":149},[92,592,593],{"class":436}," {\n",[92,595,596,599,602,605,608,610,612],{"class":94,"line":514},[92,597,598],{"class":102},"    '\"AB-100\"=\"ab-100\"'",[92,600,601],{"class":436},": ",[92,603,604],{"class":102},"\"AB-100\"",[92,606,607],{"class":436},".casefold() ",[92,609,50],{"class":149},[92,611,576],{"class":102},[92,613,614],{"class":436},".casefold(),\n",[92,616,617,620,622,624,627,629],{"class":94,"line":527},[92,618,619],{"class":102},"    'EXACT(\"AB-100\",\"ab-100\")'",[92,621,601],{"class":436},[92,623,604],{"class":102},[92,625,626],{"class":149}," ==",[92,628,576],{"class":102},[92,630,631],{"class":436},",\n",[92,633,635,638,640,642,645,647],{"class":94,"line":634},7,[92,636,637],{"class":102},"    'COUNTIF(...,\"AB-100\")'",[92,639,601],{"class":436},[92,641,500],{"class":136},[92,643,644],{"class":436},"((key ",[92,646,50],{"class":149},[92,648,649],{"class":436}," target).sum()),\n",[92,651,653,656,658,660,662,664,666],{"class":94,"line":652},8,[92,654,655],{"class":102},"    \"SUMPRODUCT(--EXACT(...))\"",[92,657,601],{"class":436},[92,659,500],{"class":136},[92,661,503],{"class":436},[92,663,50],{"class":149},[92,665,508],{"class":102},[92,667,668],{"class":436},").sum()),\n",[92,670,672,675,677,679,682,685,688],{"class":94,"line":671},9,[92,673,674],{"class":102},"    'MATCH(\"ab-100\",...,0)'",[92,676,601],{"class":436},[92,678,500],{"class":136},[92,680,681],{"class":436},"(key.eq(target).to_numpy().argmax()) ",[92,683,684],{"class":149},"+",[92,686,687],{"class":136}," 1",[92,689,631],{"class":436},[92,691,693,696,699,701],{"class":94,"line":692},10,[92,694,695],{"class":102},"    'XLOOKUP(\"ab-100\",...)'",[92,697,698],{"class":436},": codes[key.eq(target)].iloc[",[92,700,250],{"class":136},[92,702,703],{"class":436},"],\n",[92,705,707,710,712,714,717,720],{"class":94,"line":706},11,[92,708,709],{"class":102},"    'COUNTIF(...,\"ab*\")'",[92,711,601],{"class":436},[92,713,500],{"class":136},[92,715,716],{"class":436},"(key.str.startswith(",[92,718,719],{"class":102},"\"ab\"",[92,721,668],{"class":436},[92,723,725,728],{"class":94,"line":724},12,[92,726,727],{"class":102},"    \"ROWS(UNIQUE(...))\"",[92,729,730],{"class":436},": key.nunique(),\n",[92,732,734],{"class":94,"line":733},13,[92,735,736],{"class":436},"}\n",[92,738,740,743,746,749],{"class":94,"line":739},14,[92,741,742],{"class":149},"for",[92,744,745],{"class":436}," formula, value ",[92,747,748],{"class":149},"in",[92,750,751],{"class":436}," results.items():\n",[92,753,755,758,760,763,766,770,773,776,779,782,785,787,789],{"class":94,"line":754},15,[92,756,757],{"class":136},"    print",[92,759,492],{"class":436},[92,761,762],{"class":149},"f",[92,764,765],{"class":102},"\"",[92,767,769],{"class":768},"sSjpA","{",[92,771,772],{"class":436},"formula",[92,774,775],{"class":149},":28",[92,777,778],{"class":768},"}",[92,780,781],{"class":768}," {",[92,783,784],{"class":436},"value",[92,786,778],{"class":768},[92,788,765],{"class":102},[92,790,791],{"class":436},")\n",[10,793,794,795,798,799,189,801,804,805,808,809,812,813,815,816,818,819,30,821,192,823,825,826,829],{},"The pattern is the same throughout: compare a ",[14,796,797],{},"folded"," version of the text to reproduce Excel, and the original text to reproduce ",[23,800,46],{},[23,802,803],{},"str.casefold()"," is the right tool for the folding — it is like ",[23,806,807],{},"lower()"," but designed for caseless comparison, so it also handles letters such as the German ",[14,810,811],{},"ß"," consistently. Every result now matches LibreOffice: equality true, ",[23,814,46],{}," false, three matches for ",[23,817,29],{},", one for the case-sensitive count, position 1 for ",[23,820,33],{},[14,822,16],{},[23,824,36],{},", four for the wildcard and three distinct values. The lookup returns the ",[14,827,828],{},"original"," spelling of the first match, as Excel does, because the boolean mask built on the folded key selects from the unfolded column.",[228,831,237,837,237,840,237,843,237,847,237,854,237,860,237,865,237,869,237,873,237,877,237,881,237,886,237,891,237,895,237,898,237,901,237,904],{"viewBox":832,"role":231,"ariaLabelledBy":833,"xmlns":235,"style":836},"4 0 752 199",[834,835],"ex-rules-t","ex-rules-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[239,838,839],{"id":834},"Excel and pandas defaults",[243,841,842],{"id":835},"Excel's equals operator, COUNTIF, MATCH, XLOOKUP and UNIQUE ignore case, and only EXACT compares case. pandas equality, value_counts, nunique, merge and drop_duplicates all compare case, and casefold is needed to ignore it.",[247,844],{"x":346,"y":250,"width":845,"height":846,"fill":253},"752","199",[247,848],{"x":849,"y":257,"width":850,"height":851,"rx":852,"fill":293,"stroke":294,"style":853},"20.0","351.0","167.0","14","stroke-width:2px",[268,855,859],{"x":856,"y":857,"style":858},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Excel",[94,861],{"x1":862,"y1":863,"x2":864,"y2":863,"stroke":294,"style":262},"36.0","57.0","355.0",[268,866,868],{"x":856,"y":867,"style":316},"77.0","=, COUNTIF, MATCH ignore case",[268,870,872],{"x":856,"y":871,"style":316},"103.0","XLOOKUP, VLOOKUP, UNIQUE too",[268,874,876],{"x":856,"y":875,"style":316},"129.0","EXACT is case-sensitive",[268,878,880],{"x":856,"y":879,"style":316},"155.0","spaces always count",[247,882],{"x":883,"y":257,"width":850,"height":851,"rx":852,"fill":884,"stroke":885,"style":853},"389.0","#d9f4f1","var(--teal,#0f9488)",[268,887,890],{"x":888,"y":857,"style":889},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","pandas",[94,892],{"x1":893,"y1":863,"x2":894,"y2":863,"stroke":885,"style":262},"405.0","724.0",[268,896,897],{"x":888,"y":867,"style":316},"==, value_counts compare case",[268,899,900],{"x":888,"y":871,"style":316},"merge, nunique, drop_duplicates too",[268,902,903],{"x":888,"y":875,"style":316},"casefold() to ignore case",[268,905,880],{"x":888,"y":879,"style":316},[78,907,909],{"id":908},"wildcards-countif-patterns-in-pandas","Wildcards: COUNTIF patterns in pandas",[10,911,912,914,915,918,919,922,923,926,927,929,930,932,933,935,936,939],{},[23,913,29],{}," also accepts wildcards: ",[23,916,917],{},"*"," for any run of characters, ",[23,920,921],{},"?"," for exactly one, and ",[23,924,925],{},"~"," to escape a literal ",[23,928,917],{}," or ",[23,931,921],{},". The ",[23,934,222],{}," example only needed ",[23,937,938],{},"startswith",", but general patterns are easier to translate into a regular expression once and reuse:",[83,941,943],{"className":424,"code":942,"language":426,"meta":88,"style":88},"import re\n\ndef excel_pattern(pattern):\n    \"\"\"Translate a COUNTIF wildcard pattern into an anchored, case-insensitive regex.\"\"\"\n    out, i = [], 0\n    while i \u003C len(pattern):\n        ch = pattern[i]\n        if ch == \"~\" and i + 1 \u003C len(pattern):\n            out.append(re.escape(pattern[i + 1])); i += 2; continue\n        out.append(\".*\" if ch == \"*\" else \".\" if ch == \"?\" else re.escape(ch))\n        i += 1\n    return \"^\" + \"\".join(out) + \"$\"\n\nfor p in [\"ab*\", \"??-100\", \"*200\", \"AB-100\"]:\n    print(f\"{p:8} {int(codes.str.contains(excel_pattern(p), case=False, regex=True).sum())}\")\n",[23,944,945,952,956,968,973,986,1002,1012,1041,1065,1103,1113,1135,1139,1171],{"__ignoreMap":88},[92,946,947,949],{"class":94,"line":95},[92,948,433],{"class":149},[92,950,951],{"class":436}," re\n",[92,953,954],{"class":94,"line":159},[92,955,449],{"emptyLinePlaceholder":448},[92,957,958,961,965],{"class":94,"line":452},[92,959,960],{"class":149},"def",[92,962,964],{"class":963},"s_Opv"," excel_pattern",[92,966,967],{"class":436},"(pattern):\n",[92,969,970],{"class":94,"line":486},[92,971,972],{"class":102},"    \"\"\"Translate a COUNTIF wildcard pattern into an anchored, case-insensitive regex.\"\"\"\n",[92,974,975,978,980,983],{"class":94,"line":514},[92,976,977],{"class":436},"    out, i ",[92,979,25],{"class":149},[92,981,982],{"class":436}," [], ",[92,984,985],{"class":136},"0\n",[92,987,988,991,994,997,1000],{"class":94,"line":527},[92,989,990],{"class":149},"    while",[92,992,993],{"class":436}," i ",[92,995,996],{"class":149},"\u003C",[92,998,999],{"class":136}," len",[92,1001,967],{"class":436},[92,1003,1004,1007,1009],{"class":94,"line":634},[92,1005,1006],{"class":436},"        ch ",[92,1008,25],{"class":149},[92,1010,1011],{"class":436}," pattern[i]\n",[92,1013,1014,1017,1020,1022,1025,1028,1030,1032,1034,1037,1039],{"class":94,"line":652},[92,1015,1016],{"class":149},"        if",[92,1018,1019],{"class":436}," ch ",[92,1021,50],{"class":149},[92,1023,1024],{"class":102}," \"~\"",[92,1026,1027],{"class":149}," and",[92,1029,993],{"class":436},[92,1031,684],{"class":149},[92,1033,687],{"class":136},[92,1035,1036],{"class":149}," \u003C",[92,1038,999],{"class":136},[92,1040,967],{"class":436},[92,1042,1043,1046,1048,1050,1053,1056,1059,1062],{"class":94,"line":671},[92,1044,1045],{"class":436},"            out.append(re.escape(pattern[i ",[92,1047,684],{"class":149},[92,1049,687],{"class":136},[92,1051,1052],{"class":436},"])); i ",[92,1054,1055],{"class":149},"+=",[92,1057,1058],{"class":136}," 2",[92,1060,1061],{"class":436},"; ",[92,1063,1064],{"class":149},"continue\n",[92,1066,1067,1070,1073,1076,1078,1080,1083,1086,1089,1091,1093,1095,1098,1100],{"class":94,"line":692},[92,1068,1069],{"class":436},"        out.append(",[92,1071,1072],{"class":102},"\".*\"",[92,1074,1075],{"class":149}," if",[92,1077,1019],{"class":436},[92,1079,50],{"class":149},[92,1081,1082],{"class":102}," \"*\"",[92,1084,1085],{"class":149}," else",[92,1087,1088],{"class":102}," \".\"",[92,1090,1075],{"class":149},[92,1092,1019],{"class":436},[92,1094,50],{"class":149},[92,1096,1097],{"class":102}," \"?\"",[92,1099,1085],{"class":149},[92,1101,1102],{"class":436}," re.escape(ch))\n",[92,1104,1105,1108,1110],{"class":94,"line":706},[92,1106,1107],{"class":436},"        i ",[92,1109,1055],{"class":149},[92,1111,1112],{"class":136}," 1\n",[92,1114,1115,1118,1121,1124,1127,1130,1132],{"class":94,"line":724},[92,1116,1117],{"class":149},"    return",[92,1119,1120],{"class":102}," \"^\"",[92,1122,1123],{"class":149}," +",[92,1125,1126],{"class":102}," \"\"",[92,1128,1129],{"class":436},".join(out) ",[92,1131,684],{"class":149},[92,1133,1134],{"class":102}," \"$\"\n",[92,1136,1137],{"class":94,"line":733},[92,1138,449],{"emptyLinePlaceholder":448},[92,1140,1141,1143,1146,1148,1151,1154,1156,1159,1161,1164,1166,1168],{"class":94,"line":739},[92,1142,742],{"class":149},[92,1144,1145],{"class":436}," p ",[92,1147,748],{"class":149},[92,1149,1150],{"class":436}," [",[92,1152,1153],{"class":102},"\"ab*\"",[92,1155,30],{"class":436},[92,1157,1158],{"class":102},"\"??-100\"",[92,1160,30],{"class":436},[92,1162,1163],{"class":102},"\"*200\"",[92,1165,30],{"class":436},[92,1167,604],{"class":102},[92,1169,1170],{"class":436},"]:\n",[92,1172,1173,1175,1177,1179,1181,1183,1185,1188,1190,1192,1194,1197,1200,1202,1205,1207,1210,1212,1215,1218,1220,1222],{"class":94,"line":754},[92,1174,757],{"class":136},[92,1176,492],{"class":436},[92,1178,762],{"class":149},[92,1180,765],{"class":102},[92,1182,769],{"class":768},[92,1184,10],{"class":436},[92,1186,1187],{"class":149},":8",[92,1189,778],{"class":768},[92,1191,781],{"class":768},[92,1193,500],{"class":136},[92,1195,1196],{"class":436},"(codes.str.contains(excel_pattern(p), ",[92,1198,1199],{"class":468},"case",[92,1201,25],{"class":149},[92,1203,1204],{"class":136},"False",[92,1206,30],{"class":436},[92,1208,1209],{"class":468},"regex",[92,1211,25],{"class":149},[92,1213,1214],{"class":136},"True",[92,1216,1217],{"class":436},").sum())",[92,1219,778],{"class":768},[92,1221,765],{"class":102},[92,1223,791],{"class":436},[10,1225,1226,1227,17,1229,1231,1232,1234,1235,1237,1238,1241,1242,1244,1245,1248,1249,1252,1253,1255,1256,1258],{},"The translation escapes every ordinary character, turns ",[23,1228,917],{},[23,1230,921],{}," into their regex equivalents, honours the ",[23,1233,925],{}," escape, and anchors the pattern at both ends — ",[23,1236,29],{}," matches the whole cell, not a part of it. ",[23,1239,1240],{},"case=False"," gives Excel's case-insensitivity. The counts follow Excel's rules: four for ",[23,1243,222],{},", three for ",[23,1246,1247],{},"??-100"," (the trailing-space variant has one character too many), two for ",[23,1250,1251],{},"*200",", and three for the plain ",[14,1254,16],{},", the same as the ",[23,1257,29],{}," without wildcards.",[228,1260,237,1265,237,1268,237,1271,237,1274,237,1279,237,1284,237,1289,237,1294,237,1299,237,1302,237,1306,237,1309,237,1313,237,1316,237,1319,237,1323,237,1327,237,1331,237,1335,237,1338,237,1341,237,1346,237,1350,237,1353,237,1357,237,1360,237,1363,237,1369,237,1371,237,1374],{"viewBox":1261,"role":231,"ariaLabelledBy":1262,"xmlns":235,"style":836},"4 0 752 126",[1263,1264],"ex-wild-t","ex-wild-d",[239,1266,1267],{"id":1263},"Translating a COUNTIF pattern",[243,1269,1270],{"id":1264},"Each character of the pattern is examined. A tilde escapes the next character, a star becomes any run of characters, a question mark becomes exactly one character, and everything else is escaped literally. The result is anchored and matched without regard to case.",[247,1272],{"x":346,"y":250,"width":845,"height":1273,"fill":253},"126",[247,1275],{"x":849,"y":257,"width":1276,"height":1277,"rx":1278,"fill":260,"stroke":261,"style":853},"115.2","94.0","12",[268,1280,1283],{"x":1281,"y":289,"style":1282},"77.6","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","~x",[268,1285,1288],{"x":1281,"y":1286,"style":1287},"78.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","literal x",[94,1290],{"x1":1291,"y1":1292,"x2":1293,"y2":1292,"stroke":294,"style":853},"140.2","63.0","162.2",[1295,1296],"polygon",{"points":1297,"fill":1298},"166.2,63.0 157.2,68.0 157.2,58.0","#5b5cf0",[247,1300],{"x":1301,"y":257,"width":1276,"height":1277,"rx":1278,"fill":293,"stroke":294,"style":853},"171.2",[268,1303,917],{"x":1304,"y":289,"style":1305},"228.8","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[268,1307,1308],{"x":1304,"y":1286,"style":1287},"any run → .*",[94,1310],{"x1":1311,"y1":1292,"x2":1312,"y2":1292,"stroke":294,"style":853},"291.4","313.4",[1295,1314],{"points":1315,"fill":1298},"317.4,63.0 308.4,68.0 308.4,58.0",[247,1317],{"x":1318,"y":257,"width":1276,"height":1277,"rx":1278,"fill":293,"stroke":294,"style":853},"322.4",[268,1320,921],{"x":1321,"y":1322,"style":1305},"380.0","50.2",[268,1324,1326],{"x":1321,"y":1325,"style":1287},"69.8","one character",[268,1328,1330],{"x":1321,"y":1329,"style":1287},"87.2","→ .",[94,1332],{"x1":1333,"y1":1292,"x2":1334,"y2":1292,"stroke":294,"style":853},"442.6","464.6",[1295,1336],{"points":1337,"fill":1298},"468.6,63.0 459.6,68.0 459.6,58.0",[247,1339],{"x":1340,"y":257,"width":1276,"height":1277,"rx":1278,"fill":335,"stroke":336,"style":853},"473.6",[268,1342,1345],{"x":1343,"y":1322,"style":1344},"531.2","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","other",[268,1347,1349],{"x":1343,"y":1348,"style":1344},"67.8","characters",[268,1351,1352],{"x":1343,"y":1329,"style":1287},"re.escape",[94,1354],{"x1":1355,"y1":1292,"x2":1356,"y2":1292,"stroke":294,"style":853},"593.8","615.8",[1295,1358],{"points":1359,"fill":1298},"619.8,63.0 610.8,68.0 610.8,58.0",[247,1361],{"x":1362,"y":257,"width":1276,"height":1277,"rx":1278,"fill":884,"stroke":885,"style":853},"624.8",[268,1364,1368],{"x":1365,"y":1366,"style":1367},"682.4","41.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","^ … $,",[268,1370,1240],{"x":1365,"y":289,"style":1367},[268,1372,1373],{"x":1365,"y":1286,"style":1287},"whole cell, any",[268,1375,1199],{"x":1365,"y":324,"style":1287},[78,1377,1379],{"id":1378},"step-4-joins-and-duplicates","Step 4 — Joins and duplicates",[83,1381,1383],{"className":424,"code":1382,"language":426,"meta":88,"style":88},"prices = pd.DataFrame({\"Code\": [\"AB-100\", \"CD-200\"], \"Price\": [12.5, 8.0]})\norders = pd.DataFrame({\"Code\": [\"ab-100\", \"Ab-100\", \"CD-200\", \"cd-200\"], \"Qty\": [3, 1, 5, 2]})\n\nexact = orders.merge(prices, on=\"Code\", how=\"left\")\nexcel_like = (orders.assign(key=orders[\"Code\"].str.casefold())\n                    .merge(prices.assign(key=prices[\"Code\"].str.casefold()).drop(columns=\"Code\"), on=\"key\", how=\"left\")\n                    .drop(columns=\"key\"))\nprint(\"exact merge, unmatched:\", int(exact[\"Price\"].isna().sum()))\nprint(\"casefold merge, unmatched:\", int(excel_like[\"Price\"].isna().sum()))\nprint(orders.assign(key=orders[\"Code\"].str.casefold()).drop_duplicates(\"key\")[\"Code\"].tolist())\n",[23,1384,1385,1426,1479,1483,1512,1535,1579,1593,1614,1634],{"__ignoreMap":88},[92,1386,1387,1390,1392,1395,1397,1400,1402,1404,1407,1410,1413,1415,1418,1420,1423],{"class":94,"line":95},[92,1388,1389],{"class":436},"prices ",[92,1391,25],{"class":149},[92,1393,1394],{"class":436}," pd.DataFrame({",[92,1396,480],{"class":102},[92,1398,1399],{"class":436},": [",[92,1401,604],{"class":102},[92,1403,30],{"class":436},[92,1405,1406],{"class":102},"\"CD-200\"",[92,1408,1409],{"class":436},"], ",[92,1411,1412],{"class":102},"\"Price\"",[92,1414,1399],{"class":436},[92,1416,1417],{"class":136},"12.5",[92,1419,30],{"class":436},[92,1421,1422],{"class":136},"8.0",[92,1424,1425],{"class":436},"]})\n",[92,1427,1428,1431,1433,1435,1437,1439,1442,1444,1447,1449,1451,1453,1456,1458,1461,1463,1465,1467,1469,1471,1473,1475,1477],{"class":94,"line":159},[92,1429,1430],{"class":436},"orders ",[92,1432,25],{"class":149},[92,1434,1394],{"class":436},[92,1436,480],{"class":102},[92,1438,1399],{"class":436},[92,1440,1441],{"class":102},"\"ab-100\"",[92,1443,30],{"class":436},[92,1445,1446],{"class":102},"\"Ab-100\"",[92,1448,30],{"class":436},[92,1450,1406],{"class":102},[92,1452,30],{"class":436},[92,1454,1455],{"class":102},"\"cd-200\"",[92,1457,1409],{"class":436},[92,1459,1460],{"class":102},"\"Qty\"",[92,1462,1399],{"class":436},[92,1464,328],{"class":136},[92,1466,30],{"class":436},[92,1468,290],{"class":136},[92,1470,30],{"class":436},[92,1472,361],{"class":136},[92,1474,30],{"class":436},[92,1476,311],{"class":136},[92,1478,1425],{"class":436},[92,1480,1481],{"class":94,"line":452},[92,1482,449],{"emptyLinePlaceholder":448},[92,1484,1485,1488,1490,1493,1496,1498,1500,1502,1505,1507,1510],{"class":94,"line":486},[92,1486,1487],{"class":436},"exact ",[92,1489,25],{"class":149},[92,1491,1492],{"class":436}," orders.merge(prices, ",[92,1494,1495],{"class":468},"on",[92,1497,25],{"class":149},[92,1499,480],{"class":102},[92,1501,30],{"class":436},[92,1503,1504],{"class":468},"how",[92,1506,25],{"class":149},[92,1508,1509],{"class":102},"\"left\"",[92,1511,791],{"class":436},[92,1513,1514,1517,1519,1522,1525,1527,1530,1532],{"class":94,"line":514},[92,1515,1516],{"class":436},"excel_like ",[92,1518,25],{"class":149},[92,1520,1521],{"class":436}," (orders.assign(",[92,1523,1524],{"class":468},"key",[92,1526,25],{"class":149},[92,1528,1529],{"class":436},"orders[",[92,1531,480],{"class":102},[92,1533,1534],{"class":436},"].str.casefold())\n",[92,1536,1537,1540,1542,1544,1547,1549,1552,1555,1557,1559,1562,1564,1566,1569,1571,1573,1575,1577],{"class":94,"line":527},[92,1538,1539],{"class":436},"                    .merge(prices.assign(",[92,1541,1524],{"class":468},[92,1543,25],{"class":149},[92,1545,1546],{"class":436},"prices[",[92,1548,480],{"class":102},[92,1550,1551],{"class":436},"].str.casefold()).drop(",[92,1553,1554],{"class":468},"columns",[92,1556,25],{"class":149},[92,1558,480],{"class":102},[92,1560,1561],{"class":436},"), ",[92,1563,1495],{"class":468},[92,1565,25],{"class":149},[92,1567,1568],{"class":102},"\"key\"",[92,1570,30],{"class":436},[92,1572,1504],{"class":468},[92,1574,25],{"class":149},[92,1576,1509],{"class":102},[92,1578,791],{"class":436},[92,1580,1581,1584,1586,1588,1590],{"class":94,"line":634},[92,1582,1583],{"class":436},"                    .drop(",[92,1585,1554],{"class":468},[92,1587,25],{"class":149},[92,1589,1568],{"class":102},[92,1591,1592],{"class":436},"))\n",[92,1594,1595,1597,1599,1602,1604,1606,1609,1611],{"class":94,"line":652},[92,1596,489],{"class":136},[92,1598,492],{"class":436},[92,1600,1601],{"class":102},"\"exact merge, unmatched:\"",[92,1603,30],{"class":436},[92,1605,500],{"class":136},[92,1607,1608],{"class":436},"(exact[",[92,1610,1412],{"class":102},[92,1612,1613],{"class":436},"].isna().sum()))\n",[92,1615,1616,1618,1620,1623,1625,1627,1630,1632],{"class":94,"line":671},[92,1617,489],{"class":136},[92,1619,492],{"class":436},[92,1621,1622],{"class":102},"\"casefold merge, unmatched:\"",[92,1624,30],{"class":436},[92,1626,500],{"class":136},[92,1628,1629],{"class":436},"(excel_like[",[92,1631,1412],{"class":102},[92,1633,1613],{"class":436},[92,1635,1636,1638,1641,1643,1645,1647,1649,1652,1654,1656,1658],{"class":94,"line":692},[92,1637,489],{"class":136},[92,1639,1640],{"class":436},"(orders.assign(",[92,1642,1524],{"class":468},[92,1644,25],{"class":149},[92,1646,1529],{"class":436},[92,1648,480],{"class":102},[92,1650,1651],{"class":436},"].str.casefold()).drop_duplicates(",[92,1653,1568],{"class":102},[92,1655,477],{"class":436},[92,1657,480],{"class":102},[92,1659,1660],{"class":436},"].tolist())\n",[10,1662,1663,1664,1666,1667,1669,1670,1672,1673,1675,1676,1678,1679,76],{},"The same choice affects joins. A ",[23,1665,39],{}," from an order list to a price list in Excel finds a price for ",[14,1668,20],{}," because it ignores case; a pandas ",[23,1671,59],{}," on the raw codes does not, and three of the four orders come back without a price. Joining on a folded key column — and dropping it afterwards — reproduces the spreadsheet: every order priced. ",[23,1674,62],{}," on the folded key keeps the first spelling of each code, which mirrors ",[23,1677,42],{},". For merges where keys also differ in spacing or punctuation, see ",[72,1680,1682],{"href":1681},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-on-keys-that-differ-in-case-and-spacing-with-pandas\u002F","Merge on Keys That Differ in Case and Spacing with pandas",[78,1684,1686],{"id":1685},"sorting-follows-the-same-split","Sorting follows the same split",[10,1688,1689,1690,30,1693,17,1696,1699,1700,1703,1704,1706,1707,1709],{},"Sorting is the other place the difference shows up. Excel's sort ignores case by default, so ",[14,1691,1692],{},"apple",[14,1694,1695],{},"Banana",[14,1697,1698],{},"cherry"," come out in dictionary order. pandas' ",[23,1701,1702],{},"sort_values"," compares the underlying character codes, in which every capital letter comes before every lower-case one, so ",[14,1705,1695],{}," jumps ahead of ",[14,1708,1692],{},". Passing a key gives the spreadsheet order:",[83,1711,1713],{"className":424,"code":1712,"language":426,"meta":88,"style":88},"fruit = pd.Series([\"cherry\", \"Banana\", \"apple\"])\nprint(fruit.sort_values().tolist(), fruit.sort_values(key=lambda s: s.str.casefold()).tolist())\n",[23,1714,1715,1741],{"__ignoreMap":88},[92,1716,1717,1720,1722,1725,1728,1730,1733,1735,1738],{"class":94,"line":95},[92,1718,1719],{"class":436},"fruit ",[92,1721,25],{"class":149},[92,1723,1724],{"class":436}," pd.Series([",[92,1726,1727],{"class":102},"\"cherry\"",[92,1729,30],{"class":436},[92,1731,1732],{"class":102},"\"Banana\"",[92,1734,30],{"class":436},[92,1736,1737],{"class":102},"\"apple\"",[92,1739,1740],{"class":436},"])\n",[92,1742,1743,1745,1748,1750,1753],{"class":94,"line":159},[92,1744,489],{"class":136},[92,1746,1747],{"class":436},"(fruit.sort_values().tolist(), fruit.sort_values(",[92,1749,1524],{"class":468},[92,1751,1752],{"class":149},"=lambda",[92,1754,1755],{"class":436}," s: s.str.casefold()).tolist())\n",[10,1757,1758,1759,1761,1762,1765,1766,1768],{},"The first list puts ",[14,1760,1695],{}," first; the second, ",[14,1763,1764],{},"apple, Banana, cherry",", matches Excel. The ",[23,1767,1524],{}," function changes only what is compared, not what is returned, so the values keep their original spelling — the same principle as the lookups above.",[78,1770,1772],{"id":1771},"which-behaviour-should-the-report-use","Which behaviour should the report use?",[10,1774,1775],{},"Decide per column rather than globally. Identifiers that people type, such as product codes, customer references and email addresses, should usually be matched case-insensitively, as Excel did — and ideally normalised once on import so the question does not arise again. Identifiers generated by systems where case is significant, such as some SKUs, file paths on Linux, tokens and hashes, must be compared exactly. Trailing spaces are a separate problem: neither Excel nor pandas ignores them, so *AB-100 * matches nothing in either tool until it is stripped. Documenting the choice next to the code that makes it saves the next person from rediscovering why the counts differ from the old spreadsheet.",[10,1777,1778,1779,30,1781,17,1783,1785],{},"When the answer is \"case-insensitive\", the cleanest implementation is often not to fold at every comparison but to normalise the column once, right after reading: upper-case the codes, strip the spaces, and store that as the canonical value. Every later ",[23,1780,59],{},[23,1782,53],{},[23,1784,62],{}," then behaves like Excel without any special handling, and the output shows one consistent spelling instead of whichever variant happened to come first. Keep the original column alongside if anyone needs to trace a value back to the source file.",[78,1787,1789],{"id":1788},"common-pitfalls","Common pitfalls",[1791,1792,1793,1808,1820,1826,1832],"ul",{},[1794,1795,1796,1800,1801,30,1803,17,1805,1807],"li",{},[1797,1798,1799],"strong",{},"Expecting pandas to ignore case."," ",[23,1802,50],{},[23,1804,59],{},[23,1806,56],{}," are case-sensitive.",[1794,1809,1810,1800,1816,1819],{},[1797,1811,1812,1813,1815],{},"Using ",[23,1814,807],{}," for comparison.",[23,1817,1818],{},"casefold()"," is the caseless-comparison method.",[1794,1821,1822,1825],{},[1797,1823,1824],{},"Returning the folded text."," Select from the original column so lookups keep the source spelling.",[1794,1827,1828,1831],{},[1797,1829,1830],{},"Forgetting spaces."," Both tools treat a trailing space as part of the text; strip it.",[1794,1833,1834,1837],{},[1797,1835,1836],{},"One rule for every column."," Some identifiers are case-sensitive by design.",[78,1839,1841],{"id":1840},"conclusion","Conclusion",[10,1843,1844,1845,30,1847,30,1849,30,1851,30,1853,17,1855,1857,1858,1860,1861,1863,1864,1866],{},"Excel ignores case in ",[23,1846,25],{},[23,1848,29],{},[23,1850,33],{},[23,1852,36],{},[23,1854,39],{},[23,1856,42],{},", and only ",[23,1859,46],{}," compares it; pandas compares case everywhere. To reproduce a spreadsheet, compare and join on ",[23,1862,803],{}," versions of the text and select results from the original column; to reproduce ",[23,1865,46],{},", use plain equality. Decide per column, and strip spaces separately, since neither tool ignores them.",[78,1868,1870],{"id":1869},"frequently-asked-questions","Frequently asked questions",[10,1872,1873,1876,1877,1880],{},[1797,1874,1875],{},"What is the pandas equivalent of Excel's EXACT?","\nPlain equality, ",[23,1878,1879],{},"s == \"AB-100\"",", which compares case and every character.",[10,1882,1883,1886,1887,1890,1891,76],{},[1797,1884,1885],{},"How do I compare strings case-insensitively in pandas like Excel?","\nCompare ",[23,1888,1889],{},"s.str.casefold()"," with the folded target, for example ",[23,1892,1893],{},"s.str.casefold() == \"ab-100\".casefold()",[10,1895,1896,1899,1900,76],{},[1797,1897,1898],{},"Is COUNTIF case-sensitive?","\nNo; COUNTIF ignores case. Count case-sensitively in Excel with ",[23,1901,1902],{},"SUMPRODUCT(--EXACT(range, value))",[10,1904,1905,1908,1910,1911,1913,1914,1917],{},[1797,1906,1907],{},"Why does pandas count more unique values than Excel?",[23,1909,56],{}," treats different capitalisations as different values while Excel's ",[23,1912,42],{}," does not; use ",[23,1915,1916],{},"s.str.casefold().nunique()"," to match Excel.",[78,1919,1921],{"id":1920},"related","Related",[1791,1923,1924,1930,1937,1944],{},[1794,1925,1926,1927,1929],{},"Up one level: ",[72,1928,75],{"href":74}," — Excel functions in pandas.",[1794,1931,1932,1936],{},[72,1933,1935],{"href":1934},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fcountif-and-countifs-equivalent-in-pandas\u002F","COUNTIF and COUNTIFS Equivalent in pandas"," — conditional counts.",[1794,1938,1939,1943],{},[72,1940,1942],{"href":1941},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fxlookup-equivalent-in-pandas\u002F","XLOOKUP Equivalent in pandas"," — lookups in pandas.",[1794,1945,1946,1950],{},[72,1947,1949],{"href":1948},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names\u002F","Fix KeyError from Spaces in Excel Column Names"," — invisible differences in text.",[1952,1953,1954],"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 .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":88,"searchDepth":159,"depth":159,"links":1956},[1957,1958,1959,1960,1961,1962,1963,1964,1965,1966,1967,1968],{"id":80,"depth":159,"text":81},{"id":122,"depth":159,"text":123},{"id":420,"depth":159,"text":421},{"id":550,"depth":159,"text":551},{"id":908,"depth":159,"text":909},{"id":1378,"depth":159,"text":1379},{"id":1685,"depth":159,"text":1686},{"id":1771,"depth":159,"text":1772},{"id":1788,"depth":159,"text":1789},{"id":1840,"depth":159,"text":1841},{"id":1869,"depth":159,"text":1870},{"id":1920,"depth":159,"text":1921},"2026-10-02","Excel compares text without regard to case — \"AB-100\"=\"ab-100\" is TRUE, and COUNTIF, MATCH, XLOOKUP and UNIQUE all ignore case — while pandas compares exactly. Reproduce both behaviours: EXACT as plain equality, Excel-style matching with casefold, COUNTIF with wildcards, first-match lookups and distinct counts, and see why a migrated report counts six products where the spreadsheet counted three.","md",[1973,1975,1977,1979],{"q":1875,"a":1974},"Plain equality, s == \"AB-100\", which compares case and every character.",{"q":1885,"a":1976},"Compare s.str.casefold() with the folded target, for example s.str.casefold() == \"ab-100\".casefold().",{"q":1898,"a":1978},"No; COUNTIF ignores case. Count case-sensitively in Excel with SUMPRODUCT(--EXACT(range, value)).",{"q":1907,"a":1980},"nunique treats different capitalisations as different values while Excel's UNIQUE does not; use s.str.casefold().nunique() to match Excel.","excel exact function pandas",null,{"breadcrumb":1984},[1985,1988,1991],{"name":1986,"item":1987},"Home","\u002F",{"name":1989,"item":1990},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":75,"item":74},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas",{"title":5,"description":1994},"Excel ignores case in =, COUNTIF, MATCH, XLOOKUP and UNIQUE; pandas does not. EXACT as ==, Excel-style matching with casefold, wildcard counts, lookups and distinct counts, checked in LibreOffice.","exact-and-case-sensitive-comparisons-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexact-and-case-sensitive-comparisons-in-pandas\u002Findex",[1998,1999],"formula-equivalents","data-cleaning","how-to","oid6f17H3H2Q0OcwpzAc2pxdfdGaWeMmrJWthoeUPMA",[2003,2007],{"title":2004,"path":2005,"stem":2006,"children":-1},"EOMONTH and EDATE Equivalents in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Feomonth-and-edate-equivalents-in-pandas\u002Findex",{"title":2008,"path":2009,"stem":2010,"children":-1},"Excel IF Formulas as pandas Conditional Columns","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-if-formulas-as-pandas-conditional-columns","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-if-formulas-as-pandas-conditional-columns\u002Findex",[2012,2014],{"path":2013,"title":75},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2015,"title":1989},"\u002Fadvanced-data-transformation-and-cleaning",[],[2018,2021,2024,2027,2030,2033],{"path":2019,"title":2020,"kind":2000},"\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":2022,"title":2023,"kind":2000},"\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":2025,"title":2026,"kind":2000},"\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":2028,"title":2029,"kind":2000},"\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":2031,"title":2032,"kind":2000},"\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":2034,"title":2035,"kind":2000},"\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",1790972086892]