[{"data":1,"prerenderedAt":2180},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas":2148,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas":2156,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas":2161,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas":2162},{"id":4,"title":5,"body":6,"dateModified":2114,"datePublished":2114,"description":2115,"extension":2116,"faq":2117,"keyword":2126,"kind":2127,"meta":2128,"navigation":322,"path":2137,"seo":2138,"slug":2141,"stem":2142,"topics":2143,"type":2146,"__hash__":2147},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas\u002Findex.md","FIND, SEARCH and SUBSTITUTE Equivalents in pandas",{"type":7,"value":8,"toc":2100},"minimark",[9,48,217,222,250,256,260,273,435,460,464,472,528,553,670,690,694,711,856,885,1071,1077,1236,1258,1262,1278,1389,1396,1400,1425,1549,1573,1577,1590,1619,1636,1772,1794,1922,1926,1977,1981,1998,2002,2035,2039,2045,2051,2057,2063,2067,2096],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,41,42,47],"p",{},"Excel's text-search functions are the building blocks of every \"clean up this column\" formula: ",[14,15,16],"code",{},"FIND"," locates a character, ",[14,19,20],{},"SEARCH"," does the same ignoring case, ",[14,23,24],{},"SUBSTITUTE"," swaps one string for another and ",[14,27,28],{},"REPLACE"," overwrites a position. Combined with ",[14,31,32],{},"MID"," and ",[14,35,36],{},"LEN",", they pull invoice numbers out of references and normalise phone numbers — in formulas that are notoriously hard to read. pandas has direct equivalents in its ",[14,39,40],{},".str"," methods, with two differences that trip up everyone translating formulas: positions start at 0, and \"not found\" is -1 rather than an error. This page, part of ",[43,44,46],"a",{"href":45},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",", translates each function and the patterns people build from them.",[49,50,58,59,58,63,58,67,58,75,58,83,58,90,58,97,58,100,58,104,58,109,58,115,58,117,58,122,58,124,58,127,58,130,58,134,58,136,58,140,58,142,58,145,58,148,58,152,58,154,58,158,58,160,58,163,58,166,58,170,58,172,58,176,58,178,58,181,58,184,58,188,58,190,58,194,58,196,58,199,58,202,58,206,58,208,58,212,58,214],"svg",{"viewBox":51,"role":52,"ariaLabelledBy":53,"xmlns":56,"style":57},"4 0 752 276","img",[54,55],"fs-map-t","fs-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  ",[60,61,62],"title",{"id":54},"Excel text search and replace functions in pandas",[64,65,66],"desc",{"id":55},"A translation table. FIND becomes str.find plus one, with -1 meaning not found. SEARCH becomes a find on lower-cased text, or a regex with IGNORECASE. SUBSTITUTE becomes str.replace with regex False. SUBSTITUTE with an instance number needs a helper. REPLACE by position becomes str.slice_replace. MID with FIND becomes str.extract.",[68,69],"rect",{"x":70,"y":71,"width":72,"height":73,"fill":74},"4","0","752","276","#ffffff",[68,76],{"x":77,"y":78,"width":79,"height":80,"rx":71,"fill":74,"stroke":81,"style":82},"20.0","16.0","212.6","40.0","var(--line,#cdd5e6)","stroke-width:1px",[68,84],{"x":85,"y":78,"width":86,"height":80,"rx":71,"fill":87,"stroke":88,"style":89},"232.6","253.7","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.2px",[91,92,96],"text",{"x":93,"y":94,"style":95},"359.4","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","pandas",[68,98],{"x":99,"y":78,"width":86,"height":80,"rx":71,"fill":87,"stroke":88,"style":89},"486.3",[91,101,103],{"x":102,"y":94,"style":95},"613.1","Key difference",[68,105],{"x":77,"y":106,"width":79,"height":107,"rx":71,"fill":108,"stroke":81,"style":82},"56.0","34.0","#e7ebef",[91,110,114],{"x":111,"y":112,"style":113},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start","=FIND(\"-\", A2)",[68,116],{"x":85,"y":106,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,118,121],{"x":93,"y":119,"style":120},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","s.str.find(\"-\") + 1",[68,123],{"x":99,"y":106,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,125,126],{"x":102,"y":119,"style":120},"0-based; -1 if absent",[68,128],{"x":77,"y":129,"width":79,"height":107,"rx":71,"fill":108,"stroke":81,"style":82},"90.0",[91,131,133],{"x":111,"y":132,"style":113},"112.0","=SEARCH(\"inv\", A2)",[68,135],{"x":85,"y":129,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,137,139],{"x":93,"y":138,"style":120},"111.0","s.str.lower().str.find(\"inv\") + 1",[68,141],{"x":99,"y":129,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,143,144],{"x":102,"y":138,"style":120},"ignores case",[68,146],{"x":77,"y":147,"width":79,"height":107,"rx":71,"fill":108,"stroke":81,"style":82},"124.0",[91,149,151],{"x":111,"y":150,"style":113},"146.0","=SUBSTITUTE(A2, \"-\", \"\")",[68,153],{"x":85,"y":147,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,155,157],{"x":93,"y":156,"style":120},"145.0","s.str.replace(\"-\", \"\", regex=False)",[68,159],{"x":99,"y":147,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,161,162],{"x":102,"y":156,"style":120},"all occurrences",[68,164],{"x":77,"y":165,"width":79,"height":107,"rx":71,"fill":108,"stroke":81,"style":82},"158.0",[91,167,169],{"x":111,"y":168,"style":113},"180.0","=SUBSTITUTE(A2, \"(\", \"\", 2)",[68,171],{"x":85,"y":165,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,173,175],{"x":93,"y":174,"style":120},"179.0","helper for nth occurrence",[68,177],{"x":99,"y":165,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,179,180],{"x":102,"y":174,"style":120},"instance argument",[68,182],{"x":77,"y":183,"width":79,"height":107,"rx":71,"fill":108,"stroke":81,"style":82},"192.0",[91,185,187],{"x":111,"y":186,"style":113},"214.0","=REPLACE(A2, 1, 3, \"BIL\")",[68,189],{"x":85,"y":183,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,191,193],{"x":93,"y":192,"style":120},"213.0","s.str.slice_replace(0, 3, \"BIL\")",[68,195],{"x":99,"y":183,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,197,198],{"x":102,"y":192,"style":120},"by position",[68,200],{"x":77,"y":201,"width":79,"height":107,"rx":71,"fill":108,"stroke":81,"style":82},"226.0",[91,203,205],{"x":111,"y":204,"style":113},"248.0","=MID(A2, FIND(…)+1, …)",[68,207],{"x":85,"y":201,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,209,211],{"x":93,"y":210,"style":120},"247.0","s.str.extract(r\"\\((.*?)\\)\")",[68,213],{"x":99,"y":201,"width":86,"height":107,"rx":71,"fill":74,"stroke":81,"style":82},[91,215,216],{"x":102,"y":210,"style":120},"between delimiters",[218,219,221],"h2",{"id":220},"prerequisites","Prerequisites",[223,224,229],"pre",{"className":225,"code":226,"language":227,"meta":228,"style":228},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,230,231],{"__ignoreMap":228},[232,233,236,240,244,247],"span",{"class":234,"line":235},"line",1,[232,237,239],{"class":238},"sMTad","pip",[232,241,243],{"class":242},"srMev"," install",[232,245,246],{"class":242}," pandas",[232,248,249],{"class":242}," openpyxl\n",[10,251,252,255],{},[14,253,254],{},"refs.xlsx"," has four document references — mixed case, one without a customer in brackets, one with two bracketed parts — and four phone numbers written four different ways.",[218,257,259],{"id":258},"step-1-find-positions-start-at-0-and-1-means-not-found","Step 1 — FIND: positions start at 0, and -1 means not found",[10,261,262,265,266,268,269,272],{},[14,263,264],{},"str.find"," returns the position of the first match, counting from 0, or -1 when the text is absent. Excel's ",[14,267,16],{}," counts from 1 and returns ",[14,270,271],{},"#VALUE!"," when the text is absent. Convert explicitly:",[223,274,278],{"className":275,"code":276,"language":277,"meta":228,"style":228},"language-python shiki shiki-themes github-light github-dark-high-contrast","import re\nimport numpy as np\nimport pandas as pd\n\nrefs = pd.read_excel(\"refs.xlsx\")\npos0 = refs[\"ref\"].str.find(\"(\")\nrefs[\"find_excel\"] = (pos0 + 1).where(pos0 >= 0)            # like FIND(\"(\", A2), NaN for #VALUE!\nprint(refs[[\"ref\", \"find_excel\"]].to_string(index=False))\n","python",[14,279,280,290,304,317,324,342,364,404],{"__ignoreMap":228},[232,281,282,286],{"class":234,"line":235},[232,283,285],{"class":284},"s-kum","import",[232,287,289],{"class":288},"skGVy"," re\n",[232,291,293,295,298,301],{"class":234,"line":292},2,[232,294,285],{"class":284},[232,296,297],{"class":288}," numpy ",[232,299,300],{"class":284},"as",[232,302,303],{"class":288}," np\n",[232,305,307,309,312,314],{"class":234,"line":306},3,[232,308,285],{"class":284},[232,310,311],{"class":288}," pandas ",[232,313,300],{"class":284},[232,315,316],{"class":288}," pd\n",[232,318,320],{"class":234,"line":319},4,[232,321,323],{"emptyLinePlaceholder":322},true,"\n",[232,325,327,330,333,336,339],{"class":234,"line":326},5,[232,328,329],{"class":288},"refs ",[232,331,332],{"class":284},"=",[232,334,335],{"class":288}," pd.read_excel(",[232,337,338],{"class":242},"\"refs.xlsx\"",[232,340,341],{"class":288},")\n",[232,343,345,348,350,353,356,359,362],{"class":234,"line":344},6,[232,346,347],{"class":288},"pos0 ",[232,349,332],{"class":284},[232,351,352],{"class":288}," refs[",[232,354,355],{"class":242},"\"ref\"",[232,357,358],{"class":288},"].str.find(",[232,360,361],{"class":242},"\"(\"",[232,363,341],{"class":288},[232,365,367,370,373,376,378,381,384,388,391,394,397,400],{"class":234,"line":366},7,[232,368,369],{"class":288},"refs[",[232,371,372],{"class":242},"\"find_excel\"",[232,374,375],{"class":288},"] ",[232,377,332],{"class":284},[232,379,380],{"class":288}," (pos0 ",[232,382,383],{"class":284},"+",[232,385,387],{"class":386},"sP0c6"," 1",[232,389,390],{"class":288},").where(pos0 ",[232,392,393],{"class":284},">=",[232,395,396],{"class":386}," 0",[232,398,399],{"class":288},")            ",[232,401,403],{"class":402},"s-wDw","# like FIND(\"(\", A2), NaN for #VALUE!\n",[232,405,407,410,413,415,418,420,423,427,429,432],{"class":234,"line":406},8,[232,408,409],{"class":386},"print",[232,411,412],{"class":288},"(refs[[",[232,414,355],{"class":242},[232,416,417],{"class":288},", ",[232,419,372],{"class":242},[232,421,422],{"class":288},"]].to_string(",[232,424,426],{"class":425},"sa561","index",[232,428,332],{"class":284},[232,430,431],{"class":386},"False",[232,433,434],{"class":288},"))\n",[10,436,437,438,441,442,444,445,447,448,451,452,456,457,459],{},"The reference without a customer gets ",[14,439,440],{},"NaN"," where Excel would show ",[14,443,271],{},". Keeping the -1 as a number would be dangerous: ",[14,446,32],{},"-style slicing with a -1 position silently takes text from the end of the string. ",[14,449,450],{},"str.rfind"," finds the ",[453,454,455],"em",{},"last"," occurrence, which Excel can only do with a convoluted ",[14,458,24],{}," trick.",[218,461,463],{"id":462},"step-2-search-ignore-case-and-handle-wildcards","Step 2 — SEARCH: ignore case, and handle wildcards",[10,465,466,468,469,471],{},[14,467,20],{}," is ",[14,470,16],{}," without case sensitivity. Lower-case both the text and the search term:",[223,473,475],{"className":275,"code":474,"language":277,"meta":228,"style":228},"refs[\"search_inv\"] = refs[\"ref\"].str.lower().str.find(\"inv\") + 1\nprint(refs[[\"ref\", \"search_inv\"]].to_string(index=False))\n",[14,476,477,506],{"__ignoreMap":228},[232,478,479,481,484,486,488,490,492,495,498,501,503],{"class":234,"line":235},[232,480,369],{"class":288},[232,482,483],{"class":242},"\"search_inv\"",[232,485,375],{"class":288},[232,487,332],{"class":284},[232,489,352],{"class":288},[232,491,355],{"class":242},[232,493,494],{"class":288},"].str.lower().str.find(",[232,496,497],{"class":242},"\"inv\"",[232,499,500],{"class":288},") ",[232,502,383],{"class":284},[232,504,505],{"class":386}," 1\n",[232,507,508,510,512,514,516,518,520,522,524,526],{"class":234,"line":292},[232,509,409],{"class":386},[232,511,412],{"class":288},[232,513,355],{"class":242},[232,515,417],{"class":288},[232,517,483],{"class":242},[232,519,422],{"class":288},[232,521,426],{"class":425},[232,523,332],{"class":284},[232,525,431],{"class":386},[232,527,434],{"class":288},[10,529,530,531,33,534,537,538,540,541,544,545,548,549,552],{},"Both ",[14,532,533],{},"INV-…",[14,535,536],{},"inv-…"," now report position 1, and the credit note reports 0 — which, remember, means not found once 1 is added. ",[14,539,20],{}," also understands wildcards: ",[14,542,543],{},"?"," for one character and ",[14,546,547],{},"*"," for any run. Python's ",[14,550,551],{},"fnmatch.translate"," converts such a pattern to a regular expression:",[223,554,556],{"className":275,"code":555,"language":277,"meta":228,"style":228},"from fnmatch import translate\n\npattern = re.sub(r\"\\\\[Zz]$\", \"\", translate(\"inv-2026-00??*\")).replace(\"(?s:\", \"(?:\")\nprint(pattern)\nprint(refs[\"ref\"].str.contains(pattern, case=False, regex=True).tolist())\n",[14,557,558,571,575,631,638],{"__ignoreMap":228},[232,559,560,563,566,568],{"class":234,"line":235},[232,561,562],{"class":284},"from",[232,564,565],{"class":288}," fnmatch ",[232,567,285],{"class":284},[232,569,570],{"class":288}," translate\n",[232,572,573],{"class":234,"line":292},[232,574,323],{"emptyLinePlaceholder":322},[232,576,577,580,582,585,588,591,595,598,602,605,607,609,612,615,618,621,624,626,629],{"class":234,"line":306},[232,578,579],{"class":288},"pattern ",[232,581,332],{"class":284},[232,583,584],{"class":288}," re.sub(",[232,586,587],{"class":284},"r",[232,589,590],{"class":242},"\"",[232,592,594],{"class":593},"s_b0D","\\\\",[232,596,597],{"class":386},"[",[232,599,601],{"class":600},"sSjpA","Zz",[232,603,604],{"class":386},"]$",[232,606,590],{"class":242},[232,608,417],{"class":288},[232,610,611],{"class":242},"\"\"",[232,613,614],{"class":288},", translate(",[232,616,617],{"class":242},"\"inv-2026-00??*\"",[232,619,620],{"class":288},")).replace(",[232,622,623],{"class":242},"\"(?s:\"",[232,625,417],{"class":288},[232,627,628],{"class":242},"\"(?:\"",[232,630,341],{"class":288},[232,632,633,635],{"class":234,"line":319},[232,634,409],{"class":386},[232,636,637],{"class":288},"(pattern)\n",[232,639,640,642,645,647,650,653,655,657,659,662,664,667],{"class":234,"line":326},[232,641,409],{"class":386},[232,643,644],{"class":288},"(refs[",[232,646,355],{"class":242},[232,648,649],{"class":288},"].str.contains(pattern, ",[232,651,652],{"class":425},"case",[232,654,332],{"class":284},[232,656,431],{"class":386},[232,658,417],{"class":288},[232,660,661],{"class":425},"regex",[232,663,332],{"class":284},[232,665,666],{"class":386},"True",[232,668,669],{"class":288},").tolist())\n",[10,671,672,674,675,678,679,682,683,685,686,689],{},[14,673,551],{}," anchors the pattern to the end of the string, like a file-name match; removing the end anchor (",[14,676,677],{},"\\Z"," or ",[14,680,681],{},"\\z",", depending on the Python version) makes it behave like ",[14,684,20],{},". For anything more complex, write the regular expression directly — ",[14,687,688],{},"r\"INV-2026-\\d{4}\""," says exactly what it means.",[218,691,693],{"id":692},"step-3-substitute-replace-text-literally","Step 3 — SUBSTITUTE: replace text literally",[10,695,696,699,700,703,704,707,708,710],{},[14,697,698],{},"SUBSTITUTE(A2, \"-\", \"\")"," replaces every occurrence of the old text. ",[14,701,702],{},"str.replace"," with ",[14,705,706],{},"regex=False"," does the same, and several substitutions chain naturally — the pandas form of nested ",[14,709,24],{}," calls:",[223,712,714],{"className":275,"code":713,"language":277,"meta":228,"style":228},"digits = refs[\"phone\"]\nfor old in [\" \", \"-\", \".\", \"(\", \")\"]:\n    digits = digits.str.replace(old, \"\", regex=False)\nrefs[\"phone_clean\"] = digits.str.replace(r\"^\\+44\", \"0\", regex=True)\nprint(refs[[\"phone\", \"phone_clean\"]].to_string(index=False))\n",[14,715,716,731,770,792,834],{"__ignoreMap":228},[232,717,718,721,723,725,728],{"class":234,"line":235},[232,719,720],{"class":288},"digits ",[232,722,332],{"class":284},[232,724,352],{"class":288},[232,726,727],{"class":242},"\"phone\"",[232,729,730],{"class":288},"]\n",[232,732,733,736,739,742,745,748,750,753,755,758,760,762,764,767],{"class":234,"line":292},[232,734,735],{"class":284},"for",[232,737,738],{"class":288}," old ",[232,740,741],{"class":284},"in",[232,743,744],{"class":288}," [",[232,746,747],{"class":242},"\" \"",[232,749,417],{"class":288},[232,751,752],{"class":242},"\"-\"",[232,754,417],{"class":288},[232,756,757],{"class":242},"\".\"",[232,759,417],{"class":288},[232,761,361],{"class":242},[232,763,417],{"class":288},[232,765,766],{"class":242},"\")\"",[232,768,769],{"class":288},"]:\n",[232,771,772,775,777,780,782,784,786,788,790],{"class":234,"line":306},[232,773,774],{"class":288},"    digits ",[232,776,332],{"class":284},[232,778,779],{"class":288}," digits.str.replace(old, ",[232,781,611],{"class":242},[232,783,417],{"class":288},[232,785,661],{"class":425},[232,787,332],{"class":284},[232,789,431],{"class":386},[232,791,341],{"class":288},[232,793,794,796,799,801,803,806,808,810,813,816,819,821,824,826,828,830,832],{"class":234,"line":319},[232,795,369],{"class":288},[232,797,798],{"class":242},"\"phone_clean\"",[232,800,375],{"class":288},[232,802,332],{"class":284},[232,804,805],{"class":288}," digits.str.replace(",[232,807,587],{"class":284},[232,809,590],{"class":242},[232,811,812],{"class":386},"^",[232,814,815],{"class":593},"\\+",[232,817,818],{"class":242},"44\"",[232,820,417],{"class":288},[232,822,823],{"class":242},"\"0\"",[232,825,417],{"class":288},[232,827,661],{"class":425},[232,829,332],{"class":284},[232,831,666],{"class":386},[232,833,341],{"class":288},[232,835,836,838,840,842,844,846,848,850,852,854],{"class":234,"line":326},[232,837,409],{"class":386},[232,839,412],{"class":288},[232,841,727],{"class":242},[232,843,417],{"class":288},[232,845,798],{"class":242},[232,847,422],{"class":288},[232,849,426],{"class":425},[232,851,332],{"class":284},[232,853,431],{"class":386},[232,855,434],{"class":288},[10,857,858,859,862,863,866,867,869,870,873,874,877,878,881,882,872],{},"Four differently written numbers become one format. The last line switches to a regular expression on purpose, to replace ",[14,860,861],{},"+44"," only at the ",[453,864,865],{},"start",". Keep ",[14,868,706],{}," for literal replacements: with a pattern, ",[14,871,872],{},"."," matches any character and ",[14,875,876],{},"("," starts a group, so ",[14,879,880],{},"str.replace(\".\", \"\", regex=True)"," would delete every character. When a literal string has to go into a pattern, wrap it in ",[14,883,884],{},"re.escape",[49,886,58,892,58,895,58,898,58,903,58,907,58,911,58,917,58,921,58,926,58,928,58,932,58,935,58,938,58,940,58,944,58,947,58,951,58,953,58,956,58,959,58,962,58,964,58,967,58,970,58,974,58,976,58,979,58,982,58,986,58,989,58,991,58,994,58,996,58,999,58,1001,58,1003,58,1008,58,1012,58,1014,58,1016,58,1018,58,1021,58,1023,58,1025,58,1027,58,1030,58,1032,58,1034,58,1036,58,1039,58,1044,58,1047,58,1053,58,1058,58,1065],{"viewBox":887,"role":52,"ariaLabelledBy":888,"xmlns":56,"style":891},"87 0 585 220",[889,890],"fs-phone-t","fs-phone-d","width:100%;max-width:585px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[60,893,894],{"id":889},"Four phone formats normalised with chained replacements",[64,896,897],{"id":890},"Left: phone numbers written as +44 20 7946 0000, 020-7946-0001, (020) 7946 0002 and +44.20.7946.0003. Right: after removing spaces, hyphens, dots and brackets and replacing a leading +44 with 0, all four read 02079460000 to 02079460003.",[68,899],{"x":900,"y":71,"width":901,"height":902,"fill":74},"87","585","220",[68,904],{"x":905,"y":80,"width":111,"height":906,"rx":71,"fill":108,"stroke":81,"style":82},"103.9","24.0",[68,908],{"x":909,"y":80,"width":910,"height":906,"rx":71,"fill":108,"stroke":81,"style":82},"135.9","144.5",[91,912,916],{"x":913,"y":914,"style":915},"208.2","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[68,918],{"x":905,"y":919,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},"64.0","28.0",[91,922,925],{"x":923,"y":924,"style":915},"119.9","83.0","1",[68,927],{"x":909,"y":919,"width":910,"height":920,"rx":71,"fill":74,"stroke":81,"style":82},[91,929,931],{"x":913,"y":924,"style":930},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","phone",[68,933],{"x":905,"y":934,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},"92.0",[91,936,937],{"x":923,"y":138,"style":915},"2",[68,939],{"x":909,"y":934,"width":910,"height":920,"rx":71,"fill":74,"stroke":81,"style":82},[91,941,943],{"x":913,"y":138,"style":942},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","+44 20 7946 0000",[68,945],{"x":905,"y":946,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},"120.0",[91,948,950],{"x":923,"y":949,"style":915},"139.0","3",[68,952],{"x":909,"y":946,"width":910,"height":920,"rx":71,"fill":74,"stroke":81,"style":82},[91,954,955],{"x":913,"y":949,"style":942},"020-7946-0001",[68,957],{"x":905,"y":958,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},"148.0",[91,960,70],{"x":923,"y":961,"style":915},"167.0",[68,963],{"x":909,"y":958,"width":910,"height":920,"rx":71,"fill":74,"stroke":81,"style":82},[91,965,966],{"x":913,"y":961,"style":942},"(020) 7946 0002",[68,968],{"x":905,"y":969,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},"176.0",[91,971,973],{"x":923,"y":972,"style":915},"195.0","5",[68,975],{"x":909,"y":969,"width":910,"height":920,"rx":71,"fill":74,"stroke":81,"style":82},[91,977,978],{"x":913,"y":972,"style":942},"+44.20.7946.0003",[68,980],{"x":981,"y":80,"width":111,"height":906,"rx":71,"fill":108,"stroke":81,"style":82},"509.0",[68,983],{"x":984,"y":80,"width":985,"height":906,"rx":71,"fill":108,"stroke":81,"style":82},"541.0","115.1",[91,987,916],{"x":988,"y":914,"style":915},"598.5",[68,990],{"x":981,"y":919,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},[91,992,925],{"x":993,"y":924,"style":915},"525.0",[68,995],{"x":984,"y":919,"width":985,"height":920,"rx":71,"fill":74,"stroke":81,"style":82},[91,997,998],{"x":988,"y":924,"style":930},"phone_clean",[68,1000],{"x":981,"y":934,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},[91,1002,937],{"x":993,"y":138,"style":915},[68,1004],{"x":984,"y":934,"width":985,"height":920,"rx":71,"fill":1005,"stroke":1006,"style":1007},"#d9f4f1","var(--teal,#0f9488)","stroke-width:1.5px",[91,1009,1011],{"x":988,"y":138,"style":1010},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","02079460000",[68,1013],{"x":981,"y":946,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},[91,1015,950],{"x":993,"y":949,"style":915},[68,1017],{"x":984,"y":946,"width":985,"height":920,"rx":71,"fill":1005,"stroke":1006,"style":1007},[91,1019,1020],{"x":988,"y":949,"style":1010},"02079460001",[68,1022],{"x":981,"y":958,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},[91,1024,70],{"x":993,"y":961,"style":915},[68,1026],{"x":984,"y":958,"width":985,"height":920,"rx":71,"fill":1005,"stroke":1006,"style":1007},[91,1028,1029],{"x":988,"y":961,"style":1010},"02079460002",[68,1031],{"x":981,"y":969,"width":111,"height":920,"rx":71,"fill":108,"stroke":81,"style":82},[91,1033,973],{"x":993,"y":972,"style":915},[68,1035],{"x":984,"y":969,"width":985,"height":920,"rx":71,"fill":1005,"stroke":1006,"style":1007},[91,1037,1038],{"x":988,"y":972,"style":1010},"02079460003",[91,1040,931],{"x":1041,"y":1042,"style":1043},"192.2","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle",[91,1045,998],{"x":1046,"y":1042,"style":1043},"582.5",[234,1048],{"x1":1049,"y1":1050,"x2":1051,"y2":1050,"stroke":88,"style":1052},"290.4","122.0","495.0","stroke-width:2px",[1054,1055],"polygon",{"points":1056,"fill":1057},"499.0,122.0 490.0,127.0 490.0,117.0","#5b5cf0",[68,1059],{"x":1060,"y":1061,"width":1062,"height":1063,"rx":1064,"fill":74,"stroke":81,"style":89},"319.3","89.0","150.8","22.0","11",[91,1066,1070],{"x":1067,"y":1068,"style":1069},"394.7","104.0","font-size:11.5px;font-weight:400;fill:var(--brand-strong,#4338ca);text-anchor:middle;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace","replace x5 + ^\\+44",[10,1072,1073,1074,1076],{},"Long chains of nested ",[14,1075,24],{}," calls in Excel — six or eight deep to standardise company suffixes — become a dictionary in pandas, which is far easier to read and extend. Use word boundaries so a replacement only hits whole words:",[223,1078,1080],{"className":275,"code":1079,"language":277,"meta":228,"style":228},"SUFFIXES = {\"Limited\": \"Ltd\", \"Ltd.\": \"Ltd\", \"Incorporated\": \"Inc\", \"&\": \"and\"}\nnames = pd.Series([\"Acme Limited\", \"Acme Ltd.\", \"Smith & Jones Incorporated\", \"Delimited Holdings\"])\nclean = names\nfor old, new in SUFFIXES.items():\n    clean = clean.str.replace(rf\"(?\u003C!\\w){re.escape(old)}(?!\\w)\", new, regex=True)\nprint(clean.tolist())\n",[14,1081,1082,1134,1165,1175,1190,1229],{"__ignoreMap":228},[232,1083,1084,1087,1090,1093,1096,1099,1102,1104,1107,1109,1111,1113,1116,1118,1121,1123,1126,1128,1131],{"class":234,"line":235},[232,1085,1086],{"class":386},"SUFFIXES",[232,1088,1089],{"class":284}," =",[232,1091,1092],{"class":288}," {",[232,1094,1095],{"class":242},"\"Limited\"",[232,1097,1098],{"class":288},": ",[232,1100,1101],{"class":242},"\"Ltd\"",[232,1103,417],{"class":288},[232,1105,1106],{"class":242},"\"Ltd.\"",[232,1108,1098],{"class":288},[232,1110,1101],{"class":242},[232,1112,417],{"class":288},[232,1114,1115],{"class":242},"\"Incorporated\"",[232,1117,1098],{"class":288},[232,1119,1120],{"class":242},"\"Inc\"",[232,1122,417],{"class":288},[232,1124,1125],{"class":242},"\"&\"",[232,1127,1098],{"class":288},[232,1129,1130],{"class":242},"\"and\"",[232,1132,1133],{"class":288},"}\n",[232,1135,1136,1139,1141,1144,1147,1149,1152,1154,1157,1159,1162],{"class":234,"line":292},[232,1137,1138],{"class":288},"names ",[232,1140,332],{"class":284},[232,1142,1143],{"class":288}," pd.Series([",[232,1145,1146],{"class":242},"\"Acme Limited\"",[232,1148,417],{"class":288},[232,1150,1151],{"class":242},"\"Acme Ltd.\"",[232,1153,417],{"class":288},[232,1155,1156],{"class":242},"\"Smith & Jones Incorporated\"",[232,1158,417],{"class":288},[232,1160,1161],{"class":242},"\"Delimited Holdings\"",[232,1163,1164],{"class":288},"])\n",[232,1166,1167,1170,1172],{"class":234,"line":306},[232,1168,1169],{"class":288},"clean ",[232,1171,332],{"class":284},[232,1173,1174],{"class":288}," names\n",[232,1176,1177,1179,1182,1184,1187],{"class":234,"line":319},[232,1178,735],{"class":284},[232,1180,1181],{"class":288}," old, new ",[232,1183,741],{"class":284},[232,1185,1186],{"class":386}," SUFFIXES",[232,1188,1189],{"class":288},".items():\n",[232,1191,1192,1195,1197,1200,1203,1206,1209,1212,1215,1218,1221,1223,1225,1227],{"class":234,"line":326},[232,1193,1194],{"class":288},"    clean ",[232,1196,332],{"class":284},[232,1198,1199],{"class":288}," clean.str.replace(",[232,1201,1202],{"class":284},"rf",[232,1204,1205],{"class":242},"\"(?\u003C!\\w)",[232,1207,1208],{"class":600},"{",[232,1210,1211],{"class":288},"re.escape(old)",[232,1213,1214],{"class":600},"}",[232,1216,1217],{"class":242},"(?!\\w)\"",[232,1219,1220],{"class":288},", new, ",[232,1222,661],{"class":425},[232,1224,332],{"class":284},[232,1226,666],{"class":386},[232,1228,341],{"class":288},[232,1230,1231,1233],{"class":234,"line":344},[232,1232,409],{"class":386},[232,1234,1235],{"class":288},"(clean.tolist())\n",[10,1237,1238,1239,1242,1243,33,1246,1249,1250,1253,1254,1257],{},"\"Delimited Holdings\" is untouched: a plain ",[14,1240,1241],{},"SUBSTITUTE(A2,\"Limited\",\"Ltd\")"," would have produced \"DeLtd Holdings\", because it replaces inside words. The lookarounds ",[14,1244,1245],{},"(?\u003C!\\w)",[14,1247,1248],{},"(?!\\w)"," act as word boundaries that also work for replacements ending in punctuation, such as ",[14,1251,1252],{},"Ltd.",", where ",[14,1255,1256],{},"\\b"," would not. Keep the dictionary in a configuration file or a sheet so the business can add entries without a code change.",[218,1259,1261],{"id":1260},"step-4-substitute-with-an-instance-number","Step 4 — SUBSTITUTE with an instance number",[10,1263,1264,1266,1267,1270,1271,1274,1275,1277],{},[14,1265,24],{},"'s fourth argument replaces only the ",[453,1268,1269],{},"n","th occurrence — ",[14,1272,1273],{},"=SUBSTITUTE(A2, \"(\", \"[\", 2)"," changes the second bracket and leaves the first. ",[14,1276,702],{}," has no such argument, but splitting on the text and rejoining does it cleanly:",[223,1279,1281],{"className":275,"code":1280,"language":277,"meta":228,"style":228},"def substitute_nth(text, old, new, n):\n    parts = text.split(old)\n    if len(parts) \u003C= n:\n        return text                       # fewer than n occurrences: unchanged, as in Excel\n    return old.join(parts[:n]) + new + old.join(parts[n:])\n\nprint([substitute_nth(t, \"(\", \"[\", 2) for t in refs[\"ref\"]])\n",[14,1282,1283,1295,1305,1322,1333,1351,1355],{"__ignoreMap":228},[232,1284,1285,1288,1292],{"class":234,"line":235},[232,1286,1287],{"class":284},"def",[232,1289,1291],{"class":1290},"s_Opv"," substitute_nth",[232,1293,1294],{"class":288},"(text, old, new, n):\n",[232,1296,1297,1300,1302],{"class":234,"line":292},[232,1298,1299],{"class":288},"    parts ",[232,1301,332],{"class":284},[232,1303,1304],{"class":288}," text.split(old)\n",[232,1306,1307,1310,1313,1316,1319],{"class":234,"line":306},[232,1308,1309],{"class":284},"    if",[232,1311,1312],{"class":386}," len",[232,1314,1315],{"class":288},"(parts) ",[232,1317,1318],{"class":284},"\u003C=",[232,1320,1321],{"class":288}," n:\n",[232,1323,1324,1327,1330],{"class":234,"line":319},[232,1325,1326],{"class":284},"        return",[232,1328,1329],{"class":288}," text                       ",[232,1331,1332],{"class":402},"# fewer than n occurrences: unchanged, as in Excel\n",[232,1334,1335,1338,1341,1343,1346,1348],{"class":234,"line":326},[232,1336,1337],{"class":284},"    return",[232,1339,1340],{"class":288}," old.join(parts[:n]) ",[232,1342,383],{"class":284},[232,1344,1345],{"class":288}," new ",[232,1347,383],{"class":284},[232,1349,1350],{"class":288}," old.join(parts[n:])\n",[232,1352,1353],{"class":234,"line":344},[232,1354,323],{"emptyLinePlaceholder":322},[232,1356,1357,1359,1362,1364,1366,1369,1371,1373,1375,1377,1380,1382,1384,1386],{"class":234,"line":366},[232,1358,409],{"class":386},[232,1360,1361],{"class":288},"([substitute_nth(t, ",[232,1363,361],{"class":242},[232,1365,417],{"class":288},[232,1367,1368],{"class":242},"\"[\"",[232,1370,417],{"class":288},[232,1372,937],{"class":386},[232,1374,500],{"class":288},[232,1376,735],{"class":284},[232,1378,1379],{"class":288}," t ",[232,1381,741],{"class":284},[232,1383,352],{"class":288},[232,1385,355],{"class":242},[232,1387,1388],{"class":288},"]])\n",[10,1390,1391,1392,1395],{},"Only the last reference has a second bracket, and only that one changes. ",[14,1393,1394],{},"str.replace(old, new, n=1)"," covers the common \"first occurrence only\" case directly.",[218,1397,1399],{"id":1398},"count-occurrences-and-find-the-nth-one","Count occurrences and find the nth one",[10,1401,1402,1403,1406,1407,1410,1411,1414,1415,1417,1418,1420,1421,1424],{},"Two classic Excel tricks exist only because Excel lacks the functions. Counting how often a character appears is written ",[14,1404,1405],{},"=(LEN(A2)-LEN(SUBSTITUTE(A2,\"-\",\"\")))\u002FLEN(\"-\")","; in pandas it is ",[14,1408,1409],{},"str.count",". Finding the ",[453,1412,1413],{},"second"," hyphen needs ",[14,1416,16],{}," over a ",[14,1419,24],{}," that marks it with ",[14,1422,1423],{},"CHAR(1)","; in pandas, iterate over the matches:",[223,1426,1428],{"className":275,"code":1427,"language":277,"meta":228,"style":228},"print(refs[\"ref\"].str.count(re.escape(\"-\")).tolist())\n\ndef find_nth(text, sub, n):\n    positions = [m.start() + 1 for m in re.finditer(re.escape(sub), text)]\n    return positions[n - 1] if len(positions) >= n else np.nan\n\nprint([find_nth(t, \"-\", 2) for t in refs[\"ref\"]])\n",[14,1429,1430,1446,1450,1460,1485,1518,1522],{"__ignoreMap":228},[232,1431,1432,1434,1436,1438,1441,1443],{"class":234,"line":235},[232,1433,409],{"class":386},[232,1435,644],{"class":288},[232,1437,355],{"class":242},[232,1439,1440],{"class":288},"].str.count(re.escape(",[232,1442,752],{"class":242},[232,1444,1445],{"class":288},")).tolist())\n",[232,1447,1448],{"class":234,"line":292},[232,1449,323],{"emptyLinePlaceholder":322},[232,1451,1452,1454,1457],{"class":234,"line":306},[232,1453,1287],{"class":284},[232,1455,1456],{"class":1290}," find_nth",[232,1458,1459],{"class":288},"(text, sub, n):\n",[232,1461,1462,1465,1467,1470,1472,1474,1477,1480,1482],{"class":234,"line":319},[232,1463,1464],{"class":288},"    positions ",[232,1466,332],{"class":284},[232,1468,1469],{"class":288}," [m.start() ",[232,1471,383],{"class":284},[232,1473,387],{"class":386},[232,1475,1476],{"class":284}," for",[232,1478,1479],{"class":288}," m ",[232,1481,741],{"class":284},[232,1483,1484],{"class":288}," re.finditer(re.escape(sub), text)]\n",[232,1486,1487,1489,1492,1495,1497,1499,1502,1504,1507,1509,1512,1515],{"class":234,"line":326},[232,1488,1337],{"class":284},[232,1490,1491],{"class":288}," positions[n ",[232,1493,1494],{"class":284},"-",[232,1496,387],{"class":386},[232,1498,375],{"class":288},[232,1500,1501],{"class":284},"if",[232,1503,1312],{"class":386},[232,1505,1506],{"class":288},"(positions) ",[232,1508,393],{"class":284},[232,1510,1511],{"class":288}," n ",[232,1513,1514],{"class":284},"else",[232,1516,1517],{"class":288}," np.nan\n",[232,1519,1520],{"class":234,"line":344},[232,1521,323],{"emptyLinePlaceholder":322},[232,1523,1524,1526,1529,1531,1533,1535,1537,1539,1541,1543,1545,1547],{"class":234,"line":366},[232,1525,409],{"class":386},[232,1527,1528],{"class":288},"([find_nth(t, ",[232,1530,752],{"class":242},[232,1532,417],{"class":288},[232,1534,937],{"class":386},[232,1536,500],{"class":288},[232,1538,735],{"class":284},[232,1540,1379],{"class":288},[232,1542,741],{"class":284},[232,1544,352],{"class":288},[232,1546,355],{"class":242},[232,1548,1388],{"class":288},[10,1550,1551,1552,1554,1555,1557,1558,678,1560,1562,1563,1566,1567,1569,1570,1572],{},"Each reference has two hyphens except the reissued one, which has three; the second hyphen is at position 9 in all of them, Excel-style. ",[14,1553,1409],{}," takes a regular expression, which is why the hyphen is passed through ",[14,1556,884],{}," — harmless here, essential for characters such as ",[14,1559,872],{},[14,1561,876],{},". ",[14,1564,1565],{},"find_nth"," returns 1-based positions to match Excel, and ",[14,1568,440],{}," when there are fewer than ",[453,1571,1269],{}," matches.",[218,1574,1576],{"id":1575},"step-5-replace-by-position-and-midfind-for-text-between-delimiters","Step 5 — REPLACE by position, and MID(FIND()) for text between delimiters",[10,1578,1579,1580,1582,1583,1585,1586,1589],{},"Excel's ",[14,1581,28],{}," overwrites characters at a position — ",[14,1584,187],{}," swaps the first three characters. ",[14,1587,1588],{},"str.slice_replace"," takes 0-based start and stop positions:",[223,1591,1593],{"className":275,"code":1592,"language":277,"meta":228,"style":228},"print(refs[\"ref\"].str.slice_replace(0, 3, \"BIL\").tolist())\n",[14,1594,1595],{"__ignoreMap":228},[232,1596,1597,1599,1601,1603,1606,1608,1610,1612,1614,1617],{"class":234,"line":235},[232,1598,409],{"class":386},[232,1600,644],{"class":288},[232,1602,355],{"class":242},[232,1604,1605],{"class":288},"].str.slice_replace(",[232,1607,71],{"class":386},[232,1609,417],{"class":288},[232,1611,950],{"class":386},[232,1613,417],{"class":288},[232,1615,1616],{"class":242},"\"BIL\"",[232,1618,669],{"class":288},[10,1620,1621,1622,1624,1625,1627,1628,1631,1632,1635],{},"The most common combination in real workbooks is ",[14,1623,32],{}," with two ",[14,1626,16],{},"s to take the text between delimiters — ",[14,1629,1630],{},"=MID(A2, FIND(\"(\",A2)+1, FIND(\")\",A2)-FIND(\"(\",A2)-1)",". In pandas, ",[14,1633,1634],{},"str.extract"," with a pattern states the same thing directly and handles the \"not found\" case without errors:",[223,1637,1639],{"className":275,"code":1638,"language":277,"meta":228,"style":228},"refs[\"customer\"] = refs[\"ref\"].str.extract(r\"\\(([^)]*)\\)\", expand=False)\nrefs[\"year\"] = refs[\"ref\"].str.extract(r\"-(\\d{4})-\", expand=False).astype(\"Int64\")\nprint(refs[[\"ref\", \"customer\", \"year\"]].to_string(index=False))\n",[14,1640,1641,1697,1746],{"__ignoreMap":228},[232,1642,1643,1645,1648,1650,1652,1654,1656,1659,1661,1663,1666,1669,1671,1674,1677,1679,1681,1684,1686,1688,1691,1693,1695],{"class":234,"line":235},[232,1644,369],{"class":288},[232,1646,1647],{"class":242},"\"customer\"",[232,1649,375],{"class":288},[232,1651,332],{"class":284},[232,1653,352],{"class":288},[232,1655,355],{"class":242},[232,1657,1658],{"class":288},"].str.extract(",[232,1660,587],{"class":284},[232,1662,590],{"class":242},[232,1664,1665],{"class":593},"\\(",[232,1667,1668],{"class":386},"([",[232,1670,812],{"class":284},[232,1672,1673],{"class":600},")",[232,1675,1676],{"class":386},"]",[232,1678,547],{"class":284},[232,1680,1673],{"class":386},[232,1682,1683],{"class":593},"\\)",[232,1685,590],{"class":242},[232,1687,417],{"class":288},[232,1689,1690],{"class":425},"expand",[232,1692,332],{"class":284},[232,1694,431],{"class":386},[232,1696,341],{"class":288},[232,1698,1699,1701,1704,1706,1708,1710,1712,1714,1716,1719,1722,1725,1727,1730,1732,1734,1736,1738,1741,1744],{"class":234,"line":292},[232,1700,369],{"class":288},[232,1702,1703],{"class":242},"\"year\"",[232,1705,375],{"class":288},[232,1707,332],{"class":284},[232,1709,352],{"class":288},[232,1711,355],{"class":242},[232,1713,1658],{"class":288},[232,1715,587],{"class":284},[232,1717,1718],{"class":242},"\"-",[232,1720,1721],{"class":386},"(\\d",[232,1723,1724],{"class":284},"{4}",[232,1726,1673],{"class":386},[232,1728,1729],{"class":242},"-\"",[232,1731,417],{"class":288},[232,1733,1690],{"class":425},[232,1735,332],{"class":284},[232,1737,431],{"class":386},[232,1739,1740],{"class":288},").astype(",[232,1742,1743],{"class":242},"\"Int64\"",[232,1745,341],{"class":288},[232,1747,1748,1750,1752,1754,1756,1758,1760,1762,1764,1766,1768,1770],{"class":234,"line":306},[232,1749,409],{"class":386},[232,1751,412],{"class":288},[232,1753,355],{"class":242},[232,1755,417],{"class":288},[232,1757,1647],{"class":242},[232,1759,417],{"class":288},[232,1761,1703],{"class":242},[232,1763,422],{"class":288},[232,1765,426],{"class":425},[232,1767,332],{"class":284},[232,1769,431],{"class":386},[232,1771,434],{"class":288},[10,1773,1774,1775,1777,1778,1780,1781,1784,1785,1788,1789,1793],{},"The credit note gets ",[14,1776,440],{}," for the customer instead of ",[14,1779,271],{},", and the reissued invoice yields its first bracketed part, ",[14,1782,1783],{},"Fabrikam",", because ",[14,1786,1787],{},"[^)]*"," stops at the first closing bracket. ",[43,1790,1792],{"href":1791},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas\u002F","Extract Numbers and Codes from Text with pandas"," covers extraction patterns in depth.",[49,1795,58,1800,58,1803,58,1806,58,1809,58,1816,58,1827,58,1832,58,1836,58,1841,58,1847,58,1854,58,1858,58,1865,58,1869,58,1874,58,1877,58,1884,58,1889,58,1894,58,1898,58,1904,58,1908,58,1913,58,1917],{"viewBox":1796,"role":52,"ariaLabelledBy":1797,"xmlns":56,"style":57},"4 0 752 226",[1798,1799],"fs-midfind-t","fs-midfind-d",[60,1801,1802],{"id":1798},"MID with FIND versus str.extract",[64,1804,1805],{"id":1799},"Two panels for taking the customer name between brackets. The Excel formula nests MID and three FIND calls, returns #VALUE! when there are no brackets and is hard to read. The pandas str.extract pattern states the brackets and the content in one expression and returns NaN when there is no match.",[68,1807],{"x":70,"y":71,"width":72,"height":1808,"fill":74},"226",[68,1810],{"x":77,"y":78,"width":1811,"height":1812,"rx":1813,"fill":1814,"stroke":1815,"style":1052},"351.0","194.0","14","#fdefd8","var(--gold,#b4740a)",[1817,1818,1820],"g",{"transform":1819},"translate(182.5,29.0) scale(1.083)",[1821,1822],"path",{"d":1823,"fill":1824,"stroke":1825,"style":1826},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--gold-ink,#7a4e06)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[91,1828,1831],{"x":1829,"y":112,"style":1830},"195.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","=MID(A2, FIND(\"(\",A2)+1,",[91,1833,1835],{"x":1829,"y":1834,"style":1830},"97.0","FIND(\")\",A2)-FIND(\"(\",A2)-1)",[234,1837],{"x1":1838,"y1":1839,"x2":1840,"y2":1839,"stroke":1815,"style":82},"36.0","110.0","355.0",[91,1842,1846],{"x":1843,"y":1844,"style":1845},"60.0","130.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","three FIND calls",[1817,1848,1850],{"transform":1849},"translate(38.5,118.5) scale(0.625)",[1821,1851],{"d":1852,"fill":1824,"stroke":1815,"style":1853},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[91,1855,1857],{"x":1843,"y":1856,"style":1845},"156.0","#VALUE! without brackets",[1817,1859,1861],{"transform":1860},"translate(38.5,144.5) scale(0.625)",[1821,1862],{"d":1863,"fill":1824,"stroke":1864,"style":1853},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[91,1866,1868],{"x":1843,"y":1867,"style":1845},"182.0","hard to read or change",[1817,1870,1872],{"transform":1871},"translate(38.5,170.5) scale(0.625)",[1821,1873],{"d":1863,"fill":1824,"stroke":1864,"style":1853},[68,1875],{"x":1876,"y":78,"width":1811,"height":1812,"rx":1813,"fill":1005,"stroke":1006,"style":1052},"389.0",[1817,1878,1880],{"transform":1879},"translate(551.5,29.0) scale(1.083)",[1821,1881],{"d":1882,"fill":1824,"stroke":1883,"style":1826},"M9 8l-4 4 4 4 M15 8l4 4-4 4","var(--teal-ink,#0b6157)",[91,1885,1888],{"x":1886,"y":112,"style":1887},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","str.extract(r\"\\(([^)]*)\\)\")",[234,1890],{"x1":1891,"y1":1892,"x2":1893,"y2":1892,"stroke":1006,"style":82},"405.0","91.0","724.0",[91,1895,1897],{"x":1896,"y":138,"style":1845},"429.0","one pattern",[1817,1899,1901],{"transform":1900},"translate(407.5,99.5) scale(0.625)",[1821,1902],{"d":1903,"fill":1824,"stroke":1006,"style":1853},"M5 12.5l4.5 4.5L19 7",[91,1905,1907],{"x":1896,"y":1906,"style":1845},"137.0","NaN without brackets",[1817,1909,1911],{"transform":1910},"translate(407.5,125.5) scale(0.625)",[1821,1912],{"d":1903,"fill":1824,"stroke":1006,"style":1853},[91,1914,1916],{"x":1896,"y":1915,"style":1845},"163.0","easy to extend",[1817,1918,1920],{"transform":1919},"translate(407.5,151.5) scale(0.625)",[1821,1921],{"d":1903,"fill":1824,"stroke":1006,"style":1853},[218,1923,1925],{"id":1924},"common-pitfalls","Common pitfalls",[1927,1928,1929,1940,1949,1963],"ul",{},[1930,1931,1932,1936,1937,1939],"li",{},[1933,1934,1935],"strong",{},"Off-by-one positions."," ",[14,1938,264],{}," is 0-based; add 1 to compare with Excel.",[1930,1941,1942,1945,1946,1948],{},[1933,1943,1944],{},"Using -1 as a position."," Slicing with -1 takes text from the end; convert to ",[14,1947,440],{}," first.",[1930,1950,1951,1956,1957,1959,1960,1962],{},[1933,1952,1953,1954,872],{},"Regex characters in ",[14,1955,702],{}," Leave ",[14,1958,706],{}," for literal text; escape with ",[14,1961,884],{}," when mixing.",[1930,1964,1965,1968,1969,1972,1973,1976],{},[1933,1966,1967],{},"Case in SEARCH translations."," Lower-case both sides, or pass ",[14,1970,1971],{},"case=False"," to ",[14,1974,1975],{},"str.contains","; forgetting it is the most common reason a translated formula finds fewer rows than Excel did.",[218,1978,1980],{"id":1979},"performance-notes","Performance notes",[10,1982,1983,1984,1986,1987,1989,1990,1993,1994,1997],{},"The ",[14,1985,40],{}," methods loop in optimised code and handle hundreds of thousands of strings per second. Chaining five ",[14,1988,702],{}," calls is still fast; for dozens of substitutions, one regular expression with alternatives — ",[14,1991,1992],{},"str.replace(r\"[\\s\\-.()]\", \"\", regex=True)"," — does a single pass. Row-wise helpers such as ",[14,1995,1996],{},"substitute_nth"," run in Python and are best kept to the columns that need them.",[218,1999,2001],{"id":2000},"conclusion","Conclusion",[10,2003,2004,2006,2007,2010,2011,2013,2014,2016,2017,2019,2020,2022,2023,1936,2025,2027,2028,2031,2032,2034],{},[14,2005,16],{}," becomes ",[14,2008,2009],{},"str.find(...) + 1"," with -1 converted to missing, ",[14,2012,20],{}," a find on lower-cased text or a case-insensitive regex (with ",[14,2015,551],{}," for wildcards), ",[14,2018,24],{}," a literal ",[14,2021,702],{}," — chained for nested substitutions, with a split-and-join helper for the instance argument — and ",[14,2024,28],{},[14,2026,1588],{},". The ubiquitous ",[14,2029,2030],{},"MID(FIND())"," pattern is best rewritten as ",[14,2033,1634],{},", which is shorter and fails gracefully.",[218,2036,2038],{"id":2037},"frequently-asked-questions","Frequently asked questions",[10,2040,2041,2044],{},[1933,2042,2043],{},"Why does str.find return a different number from FIND?","\nExcel positions start at 1 and pandas positions start at 0, so str.find returns one less. A missing substring is #VALUE! in Excel and -1 in pandas. Add 1 and treat -1 as missing to reproduce FIND exactly.",[10,2046,2047,2050],{},[1933,2048,2049],{},"What is the difference between FIND and SEARCH?","\nFIND is case-sensitive and SEARCH is not; SEARCH also accepts the wildcards ? and *. In pandas, lower-case both sides for a case-insensitive search, and convert wildcards to a regular expression with fnmatch.translate.",[10,2052,2053,2056],{},[1933,2054,2055],{},"How do I replace only the second occurrence, like SUBSTITUTE's instance argument?","\nstr.replace replaces all occurrences, or the first n with n=. For a specific occurrence, split on the text and rejoin, or use a regular expression that skips the earlier matches, as shown on this page.",[10,2058,2059,2062],{},[1933,2060,2061],{},"Does str.replace treat the pattern as a regular expression?","\nNot by default in current pandas: regex defaults to False, so characters such as . and ( are matched literally. Pass regex=True when you want a pattern, and escape special characters with re.escape when a literal string must go into one.",[218,2064,2066],{"id":2065},"related","Related",[1927,2068,2069,2075,2082,2089],{},[1930,2070,2071,2072,2074],{},"Up one level: ",[43,2073,46],{"href":45}," — every function, translated.",[1930,2076,2077,2081],{},[43,2078,2080],{"href":2079},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-text-functions-left-right-mid-and-concat-in-pandas\u002F","Excel Text Functions LEFT, RIGHT, MID and CONCAT in pandas"," — position-based text functions.",[1930,2083,2084,2088],{},[43,2085,2087],{"href":2086},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Ffind-and-replace-text-in-excel-with-openpyxl\u002F","Find and Replace Text in Excel with openpyxl"," — replacing text inside a workbook rather than a DataFrame.",[1930,2090,2091,2095],{},[43,2092,2094],{"href":2093},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstrip-whitespace-and-normalise-text-columns-with-pandas\u002F","Strip Whitespace and Normalise Text Columns with pandas"," — the clean-up that usually comes first.",[2097,2098,2099],"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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_b0D, html code.shiki .s_b0D{--shiki-default:#22863A;--shiki-default-font-weight:bold;--shiki-dark:#72F088;--shiki-dark-font-weight:bold}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":228,"searchDepth":292,"depth":292,"links":2101},[2102,2103,2104,2105,2106,2107,2108,2109,2110,2111,2112,2113],{"id":220,"depth":292,"text":221},{"id":258,"depth":292,"text":259},{"id":462,"depth":292,"text":463},{"id":692,"depth":292,"text":693},{"id":1260,"depth":292,"text":1261},{"id":1398,"depth":292,"text":1399},{"id":1575,"depth":292,"text":1576},{"id":1924,"depth":292,"text":1925},{"id":1979,"depth":292,"text":1980},{"id":2000,"depth":292,"text":2001},{"id":2037,"depth":292,"text":2038},{"id":2065,"depth":292,"text":2066},"2026-09-25","Translate Excel's FIND, SEARCH, SUBSTITUTE and REPLACE to pandas string methods: positions that start at 0 instead of 1, -1 instead of #VALUE!, case-insensitive search, Excel wildcards, replacing only the second occurrence, and the MID(FIND()) pattern for text between delimiters.","md",[2118,2120,2122,2124],{"q":2043,"a":2119},"Excel positions start at 1 and pandas positions start at 0, so str.find returns one less. A missing substring is #VALUE! in Excel and -1 in pandas. Add 1 and treat -1 as missing to reproduce FIND exactly.",{"q":2049,"a":2121},"FIND is case-sensitive and SEARCH is not; SEARCH also accepts the wildcards ? and *. In pandas, lower-case both sides for a case-insensitive search, and convert wildcards to a regular expression with fnmatch.translate.",{"q":2055,"a":2123},"str.replace replaces all occurrences, or the first n with n=. For a specific occurrence, split on the text and rejoin, or use a regular expression that skips the earlier matches, as shown on this page.",{"q":2061,"a":2125},"Not by default in current pandas: regex defaults to False, so characters such as . and ( are matched literally. Pass regex=True when you want a pattern, and escape special characters with re.escape when a literal string must go into one.","find search and substitute equivalents in pandas",null,{"breadcrumb":2129},[2130,2133,2136],{"name":2131,"item":2132},"Home","\u002F",{"name":2134,"item":2135},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":46,"item":45},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas",{"title":2139,"description":2140},"Excel FIND, SEARCH, SUBSTITUTE and REPLACE in pandas","Excel text functions in pandas: str.find versus FIND positions, SEARCH case-insensitively and with wildcards, SUBSTITUTE with str.replace including the nth instance, REPLACE with slice_replace, and extracting text between delimiters.","find-search-and-substitute-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas\u002Findex",[2144,2145,96],"formula-equivalents","data-cleaning","how-to","vMVRM6u02HFQSX1eTLodJkm_Ev7rtSkerzWiPKEUCVE",[2149,2152],{"title":2080,"path":2150,"stem":2151,"children":-1},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-text-functions-left-right-mid-and-concat-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fexcel-text-functions-left-right-mid-and-concat-in-pandas\u002Findex",{"title":2153,"path":2154,"stem":2155,"children":-1},"FORECAST, TREND, SLOPE and RSQ Equivalents in Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fforecast-trend-and-slope-equivalents-in-python\u002Findex",[2157,2159],{"path":2158,"title":46},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2160,"title":2134},"\u002Fadvanced-data-transformation-and-cleaning",[],[2163,2166,2168,2171,2174,2177],{"path":2164,"title":2165,"kind":2146},"\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":2167,"title":1792,"kind":2146},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas",{"path":2169,"title":2170,"kind":2146},"\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":2172,"title":2173,"kind":2146},"\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":2175,"title":2176,"kind":2146},"\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":2178,"title":2179,"kind":2146},"\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",1790972086995]