[{"data":1,"prerenderedAt":2490},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas":2457,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas":2466,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas":2471,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas":2472},{"id":4,"title":5,"body":6,"dateModified":2424,"datePublished":2424,"description":2425,"extension":2426,"faq":2427,"keyword":2436,"kind":2437,"meta":2438,"navigation":121,"path":2446,"seo":2447,"slug":2450,"stem":2451,"topics":2452,"type":2455,"__hash__":2456},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas\u002Findex.md","REGEXTEST, REGEXEXTRACT and REGEXREPLACE Equivalents in pandas",{"type":7,"value":8,"toc":2410},"minimark",[9,40,45,73,83,87,93,271,318,322,328,503,535,711,715,726,856,870,874,902,1102,1118,1122,1144,1467,1478,1622,1626,1640,1858,1880,1884,1887,2078,2208,2220,2224,2299,2303,2345,2349,2355,2361,2367,2373,2377,2406],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,39],"p",{},"Excel finally has regular expressions: ",[14,15,16],"code",{},"REGEXTEST"," checks whether text matches a pattern, ",[14,19,20],{},"REGEXEXTRACT"," pulls matching text out, and ",[14,23,24],{},"REGEXREPLACE"," rewrites it. Spreadsheet users are now writing formulas like ",[14,27,28],{},"=REGEXEXTRACT(A2, \"INV-\\d{4}-\\d+\")"," to fish invoice numbers out of free-text notes. pandas has offered the same operations for years through its ",[14,31,32],{},".str"," methods, so translating these formulas is mostly mechanical — but the arguments are arranged differently, replacement strings use a different syntax for groups, and Excel's \"nth occurrence\" option has no direct pandas parameter. This page maps each function, shows the differences on a realistic column and gives tested pandas code. It belongs to ",[35,36,38],"a",{"href":37},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002F","Excel Formula Equivalents in pandas",".",[41,42,44],"h2",{"id":43},"prerequisites","Prerequisites",[46,47,52],"pre",{"className":48,"code":49,"language":50,"meta":51,"style":51},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,53,54],{"__ignoreMap":51},[55,56,59,63,67,70],"span",{"class":57,"line":58},"line",1,[55,60,62],{"class":61},"sMTad","pip",[55,64,66],{"class":65},"srMev"," install",[55,68,69],{"class":65}," pandas",[55,71,72],{"class":65}," openpyxl\n",[10,74,75,78,79,82],{},[14,76,77],{},"payment_notes.xlsx"," has one column of free-text payment notes. Some mention one invoice number, one mentions two, one writes the prefix in lower case, one has none, one is empty. The Excel functions discussed here are available in Excel 365; LibreOffice does not evaluate them (version 26.2 returned ",[14,80,81],{},"#NAME?","), so the spreadsheet behaviour described follows Excel's documentation.",[41,84,86],{"id":85},"step-1-regextest-is-strcontains","Step 1 — REGEXTEST is str.contains",[10,88,89,92],{},[14,90,91],{},"=REGEXTEST(A2, \"INV-\\d{4}-\\d+\")"," returns TRUE or FALSE. Its optional third argument chooses case sensitivity: 0 (the default) is case-sensitive, 1 ignores case.",[46,94,98],{"className":95,"code":96,"language":97,"meta":51,"style":51},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"payment_notes.xlsx\", keep_default_na=False)\npattern = r\"INV-\\d{4}-\\d+\"\ndf[\"HasInvoice\"] = df[\"Note\"].str.contains(pattern, regex=True)\ndf[\"HasInvoice (any case)\"] = df[\"Note\"].str.contains(pattern, regex=True, case=False)\nprint(df[[\"HasInvoice\", \"HasInvoice (any case)\"]].to_string())\n","python",[14,99,100,116,123,154,185,218,253],{"__ignoreMap":51},[55,101,102,106,110,113],{"class":57,"line":58},[55,103,105],{"class":104},"s-kum","import",[55,107,109],{"class":108},"skGVy"," pandas ",[55,111,112],{"class":104},"as",[55,114,115],{"class":108}," pd\n",[55,117,119],{"class":57,"line":118},2,[55,120,122],{"emptyLinePlaceholder":121},true,"\n",[55,124,126,129,132,135,138,141,145,147,151],{"class":57,"line":125},3,[55,127,128],{"class":108},"df ",[55,130,131],{"class":104},"=",[55,133,134],{"class":108}," pd.read_excel(",[55,136,137],{"class":65},"\"payment_notes.xlsx\"",[55,139,140],{"class":108},", ",[55,142,144],{"class":143},"sa561","keep_default_na",[55,146,131],{"class":104},[55,148,150],{"class":149},"sP0c6","False",[55,152,153],{"class":108},")\n",[55,155,157,160,162,165,168,171,174,177,179,182],{"class":57,"line":156},4,[55,158,159],{"class":108},"pattern ",[55,161,131],{"class":104},[55,163,164],{"class":104}," r",[55,166,167],{"class":65},"\"INV-",[55,169,170],{"class":149},"\\d",[55,172,173],{"class":104},"{4}",[55,175,176],{"class":65},"-",[55,178,170],{"class":149},[55,180,181],{"class":104},"+",[55,183,184],{"class":65},"\"\n",[55,186,188,191,194,197,199,202,205,208,211,213,216],{"class":57,"line":187},5,[55,189,190],{"class":108},"df[",[55,192,193],{"class":65},"\"HasInvoice\"",[55,195,196],{"class":108},"] ",[55,198,131],{"class":104},[55,200,201],{"class":108}," df[",[55,203,204],{"class":65},"\"Note\"",[55,206,207],{"class":108},"].str.contains(pattern, ",[55,209,210],{"class":143},"regex",[55,212,131],{"class":104},[55,214,215],{"class":149},"True",[55,217,153],{"class":108},[55,219,221,223,226,228,230,232,234,236,238,240,242,244,247,249,251],{"class":57,"line":220},6,[55,222,190],{"class":108},[55,224,225],{"class":65},"\"HasInvoice (any case)\"",[55,227,196],{"class":108},[55,229,131],{"class":104},[55,231,201],{"class":108},[55,233,204],{"class":65},[55,235,207],{"class":108},[55,237,210],{"class":143},[55,239,131],{"class":104},[55,241,215],{"class":149},[55,243,140],{"class":108},[55,245,246],{"class":143},"case",[55,248,131],{"class":104},[55,250,150],{"class":149},[55,252,153],{"class":108},[55,254,256,259,262,264,266,268],{"class":57,"line":255},7,[55,257,258],{"class":149},"print",[55,260,261],{"class":108},"(df[[",[55,263,193],{"class":65},[55,265,140],{"class":108},[55,267,225],{"class":65},[55,269,270],{"class":108},"]].to_string())\n",[10,272,273,274,277,278,281,282,285,286,289,290,292,293,296,297,300,301,303,304,307,308,310,311,313,314,317],{},"The case-sensitive test misses the note that wrote ",[14,275,276],{},"inv-2026-0107"," in lower case — but still returns TRUE for that row, because the same note also contains ",[14,279,280],{},"INV-2026-0108",". ",[14,283,284],{},"case=False"," is the equivalent of passing 1. Use a raw string (",[14,287,288],{},"r\"…\"",") for patterns in Python so that ",[14,291,170],{}," reaches the regex engine intact. ",[14,294,295],{},"keep_default_na=False"," keeps the empty note as an empty string rather than ",[14,298,299],{},"NaN","; with ",[14,302,299],{}," in the column, ",[14,305,306],{},"str.contains"," returns ",[14,309,299],{}," for that row instead of ",[14,312,150],{}," — pass ",[14,315,316],{},"na=False"," when you cannot control the input.",[41,319,321],{"id":320},"step-2-regexextracts-three-return-modes","Step 2 — REGEXEXTRACT's three return modes",[10,323,324,327],{},[14,325,326],{},"REGEXEXTRACT(text, pattern, return_mode)"," returns the first match (mode 0, the default), all matches as an array (mode 1), or the capture groups of the first match as an array (mode 2). Each has a pandas counterpart:",[46,329,331],{"className":95,"code":330,"language":97,"meta":51,"style":51},"first = df[\"Note\"].str.extract(r\"(INV-\\d{4}-\\d+)\", expand=False, flags=0)\nall_matches = df[\"Note\"].str.findall(r\"INV-\\d{4}-\\d+\")\ngroups = df[\"Note\"].str.extract(r\"INV-(?P\u003CYear>\\d{4})-(?P\u003CNumber>\\d+)\")\nprint(pd.concat([first.rename(\"first match\"), all_matches.rename(\"all matches\"), groups], axis=1).to_string())\n",[14,332,333,395,427,473],{"__ignoreMap":51},[55,334,335,338,340,342,344,347,350,353,356,359,361,363,365,367,369,372,374,376,379,381,383,385,388,390,393],{"class":57,"line":58},[55,336,337],{"class":108},"first ",[55,339,131],{"class":104},[55,341,201],{"class":108},[55,343,204],{"class":65},[55,345,346],{"class":108},"].str.extract(",[55,348,349],{"class":104},"r",[55,351,352],{"class":65},"\"",[55,354,355],{"class":149},"(",[55,357,358],{"class":65},"INV-",[55,360,170],{"class":149},[55,362,173],{"class":104},[55,364,176],{"class":65},[55,366,170],{"class":149},[55,368,181],{"class":104},[55,370,371],{"class":149},")",[55,373,352],{"class":65},[55,375,140],{"class":108},[55,377,378],{"class":143},"expand",[55,380,131],{"class":104},[55,382,150],{"class":149},[55,384,140],{"class":108},[55,386,387],{"class":143},"flags",[55,389,131],{"class":104},[55,391,392],{"class":149},"0",[55,394,153],{"class":108},[55,396,397,400,402,404,406,409,411,413,415,417,419,421,423,425],{"class":57,"line":118},[55,398,399],{"class":108},"all_matches ",[55,401,131],{"class":104},[55,403,201],{"class":108},[55,405,204],{"class":65},[55,407,408],{"class":108},"].str.findall(",[55,410,349],{"class":104},[55,412,167],{"class":65},[55,414,170],{"class":149},[55,416,173],{"class":104},[55,418,176],{"class":65},[55,420,170],{"class":149},[55,422,181],{"class":104},[55,424,352],{"class":65},[55,426,153],{"class":108},[55,428,429,432,434,436,438,440,442,444,446,450,452,454,456,458,460,463,465,467,469,471],{"class":57,"line":125},[55,430,431],{"class":108},"groups ",[55,433,131],{"class":104},[55,435,201],{"class":108},[55,437,204],{"class":65},[55,439,346],{"class":108},[55,441,349],{"class":104},[55,443,167],{"class":65},[55,445,355],{"class":149},[55,447,449],{"class":448},"sx1sP","?P\u003CYear>",[55,451,170],{"class":149},[55,453,173],{"class":104},[55,455,371],{"class":149},[55,457,176],{"class":65},[55,459,355],{"class":149},[55,461,462],{"class":448},"?P\u003CNumber>",[55,464,170],{"class":149},[55,466,181],{"class":104},[55,468,371],{"class":149},[55,470,352],{"class":65},[55,472,153],{"class":108},[55,474,475,477,480,483,486,489,492,495,497,500],{"class":57,"line":156},[55,476,258],{"class":149},[55,478,479],{"class":108},"(pd.concat([first.rename(",[55,481,482],{"class":65},"\"first match\"",[55,484,485],{"class":108},"), all_matches.rename(",[55,487,488],{"class":65},"\"all matches\"",[55,490,491],{"class":108},"), groups], ",[55,493,494],{"class":143},"axis",[55,496,131],{"class":104},[55,498,499],{"class":149},"1",[55,501,502],{"class":108},").to_string())\n",[10,504,505,508,509,512,513,515,516,518,519,522,523,526,527,530,531,534],{},[14,506,507],{},"str.extract"," returns the first match of the first capture group, so the whole pattern is wrapped in parentheses for mode 0. ",[14,510,511],{},"str.findall"," returns a list per row — mode 1. With named groups, ",[14,514,507],{}," returns one column per group — mode 2, but with headers, which a spreadsheet array cannot have. Rows without a match give ",[14,517,299],{}," (or an empty list from ",[14,520,521],{},"findall","), where Excel returns ",[14,524,525],{},"#N\u002FA","; wrap the formula in ",[14,528,529],{},"IFERROR"," in Excel and use ",[14,532,533],{},"fillna"," in pandas to show something friendlier.",[536,537,545,546,545,550,545,554,545,561,545,569,545,576,545,579,545,584,545,587,545,591,545,598,545,603,545,607,545,611,545,615,545,619,545,621,545,623,545,625,545,627,545,629,545,631,545,633,545,635,545,637,545,639,545,641,545,644,545,647,545,651,545,653,545,656,545,658,545,661,545,664,545,668,545,670,545,673,545,675,545,678,545,683,545,686,545,692,545,697,545,705],"svg",{"viewBox":538,"role":539,"ariaLabelledBy":540,"xmlns":543,"style":544},"4 0 752 196","img",[541,542],"rx-modes-t","rx-modes-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  ",[547,548,549],"title",{"id":541},"REGEXEXTRACT's return modes on one note",[551,552,553],"desc",{"id":542},"The note Partial — inv-2026-0107 and INV-2026-0108 — gives INV-2026-0108 as the first case-sensitive match (mode 0), a list with that one match for mode 1, and the groups 2026 and 0108 for mode 2. Ignoring case finds both invoice numbers.",[555,556],"rect",{"x":557,"y":392,"width":558,"height":559,"fill":560},"4","752","196","#ffffff",[555,562],{"x":563,"y":564,"width":565,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},"20.0","40.0","161.5","28.0","var(--line,#cdd5e6)","stroke-width:1px",[570,571,575],"text",{"x":572,"y":573,"style":574},"100.8","59.0","font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Note",[555,577],{"x":563,"y":578,"width":565,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},"68.0",[570,580,583],{"x":572,"y":581,"style":582},"87.0","font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Partial: inv-2026-0107 and",[555,585],{"x":563,"y":586,"width":565,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},"96.0",[570,588,590],{"x":572,"y":589,"style":582},"115.0","INV-2026-0108, chase ACME",[555,592],{"x":593,"y":564,"width":594,"height":566,"rx":392,"fill":595,"stroke":596,"style":597},"437.3","73.0","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[570,599,602],{"x":600,"y":573,"style":601},"473.9","font-size:10.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","mode",[555,604],{"x":605,"y":564,"width":606,"height":566,"rx":392,"fill":595,"stroke":596,"style":597},"510.4","89.2",[570,608,610],{"x":609,"y":573,"style":601},"555.0","pandas",[555,612],{"x":613,"y":564,"width":614,"height":566,"rx":392,"fill":595,"stroke":596,"style":597},"599.6","140.4",[570,616,618],{"x":617,"y":573,"style":601},"669.8","result",[555,620],{"x":593,"y":578,"width":594,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,622,392],{"x":600,"y":581,"style":582},[555,624],{"x":605,"y":578,"width":606,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,626,507],{"x":609,"y":581,"style":582},[555,628],{"x":613,"y":578,"width":614,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,630,280],{"x":617,"y":581,"style":582},[555,632],{"x":593,"y":586,"width":594,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,634,499],{"x":600,"y":589,"style":582},[555,636],{"x":605,"y":586,"width":606,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,638,511],{"x":609,"y":589,"style":582},[555,640],{"x":613,"y":586,"width":614,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,642,643],{"x":617,"y":589,"style":582},"[INV-2026-0108]",[555,645],{"x":593,"y":646,"width":594,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},"124.0",[570,648,650],{"x":600,"y":649,"style":582},"143.0","2",[555,652],{"x":605,"y":646,"width":606,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,654,655],{"x":609,"y":649,"style":582},"named groups",[555,657],{"x":613,"y":646,"width":614,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,659,660],{"x":617,"y":649,"style":582},"2026 | 0108",[555,662],{"x":593,"y":663,"width":594,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},"152.0",[570,665,667],{"x":600,"y":666,"style":582},"171.0","1, any case",[555,669],{"x":605,"y":663,"width":606,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,671,672],{"x":609,"y":666,"style":582},"flags=re.I",[555,674],{"x":613,"y":663,"width":614,"height":566,"rx":392,"fill":560,"stroke":567,"style":568},[570,676,677],{"x":617,"y":666,"style":582},"[inv-…0107, INV-…0108]",[570,679,682],{"x":572,"y":680,"style":681},"29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","note",[570,684,618],{"x":685,"y":680,"style":681},"588.7",[57,687],{"x1":688,"y1":689,"x2":690,"y2":689,"stroke":596,"style":691},"191.5","82.0","423.3","stroke-width:2px",[693,694],"polygon",{"points":695,"fill":696},"427.3,82.0 418.3,87.0 418.3,77.0","#5b5cf0",[555,698],{"x":699,"y":700,"width":701,"height":702,"rx":703,"fill":560,"stroke":567,"style":704},"223.1","49.0","172.7","22.0","11","stroke-width:1.2px",[570,706,710],{"x":707,"y":708,"style":709},"309.4","64.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","str.extract \u002F findall",[41,712,714],{"id":713},"step-3-case-insensitive-extraction","Step 3 — Case-insensitive extraction",[10,716,717,719,720,722,723,725],{},[14,718,507],{}," and ",[14,721,511],{}," take regular-expression flags instead of a ",[14,724,246],{}," argument:",[46,727,729],{"className":95,"code":728,"language":97,"meta":51,"style":51},"import re\n\nany_case = df[\"Note\"].str.findall(r\"INV-\\d{4}-\\d+\", flags=re.IGNORECASE)\nnormalised = any_case.map(lambda xs: [x.upper() for x in xs])\nprint(normalised.to_string())\nprint(\"distinct invoices:\", sorted({x for xs in normalised for x in xs}))\n",[14,730,731,738,742,785,813,820],{"__ignoreMap":51},[55,732,733,735],{"class":57,"line":58},[55,734,105],{"class":104},[55,736,737],{"class":108}," re\n",[55,739,740],{"class":57,"line":118},[55,741,122],{"emptyLinePlaceholder":121},[55,743,744,747,749,751,753,755,757,759,761,763,765,767,769,771,773,775,777,780,783],{"class":57,"line":125},[55,745,746],{"class":108},"any_case ",[55,748,131],{"class":104},[55,750,201],{"class":108},[55,752,204],{"class":65},[55,754,408],{"class":108},[55,756,349],{"class":104},[55,758,167],{"class":65},[55,760,170],{"class":149},[55,762,173],{"class":104},[55,764,176],{"class":65},[55,766,170],{"class":149},[55,768,181],{"class":104},[55,770,352],{"class":65},[55,772,140],{"class":108},[55,774,387],{"class":143},[55,776,131],{"class":104},[55,778,779],{"class":108},"re.",[55,781,782],{"class":149},"IGNORECASE",[55,784,153],{"class":108},[55,786,787,790,792,795,798,801,804,807,810],{"class":57,"line":156},[55,788,789],{"class":108},"normalised ",[55,791,131],{"class":104},[55,793,794],{"class":108}," any_case.map(",[55,796,797],{"class":104},"lambda",[55,799,800],{"class":108}," xs: [x.upper() ",[55,802,803],{"class":104},"for",[55,805,806],{"class":108}," x ",[55,808,809],{"class":104},"in",[55,811,812],{"class":108}," xs])\n",[55,814,815,817],{"class":57,"line":187},[55,816,258],{"class":149},[55,818,819],{"class":108},"(normalised.to_string())\n",[55,821,822,824,826,829,831,834,837,839,842,844,847,849,851,853],{"class":57,"line":220},[55,823,258],{"class":149},[55,825,355],{"class":108},[55,827,828],{"class":65},"\"distinct invoices:\"",[55,830,140],{"class":108},[55,832,833],{"class":149},"sorted",[55,835,836],{"class":108},"({x ",[55,838,803],{"class":104},[55,840,841],{"class":108}," xs ",[55,843,809],{"class":104},[55,845,846],{"class":108}," normalised ",[55,848,803],{"class":104},[55,850,806],{"class":108},[55,852,809],{"class":104},[55,854,855],{"class":108}," xs}))\n",[10,857,858,859,862,863,865,866,39],{},"With ",[14,860,861],{},"re.IGNORECASE"," the lower-case ",[14,864,276],{}," is found too, and upper-casing the matches gives one spelling per invoice. Exploding the lists into rows — one row per invoice mentioned — is a common next step for matching notes against an invoice table, shown in ",[35,867,869],{"href":868},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fsplit-delimited-cell-values-into-rows-with-pandas\u002F","Split Delimited Cell Values into Rows with pandas",[41,871,873],{"id":872},"step-4-regexreplace-is-strreplace-with-a-different-group-syntax","Step 4 — REGEXREPLACE is str.replace, with a different group syntax",[10,875,876,879,880,140,883,886,887,890,891,140,894,897,898,901],{},[14,877,878],{},"=REGEXREPLACE(A2, \"INV-(\\d{4})-(\\d+)\", \"$2\u002F$1\")"," rewrites each invoice number as number\u002Fyear. Excel refers to groups as ",[14,881,882],{},"$1",[14,884,885],{},"$2","; Python's ",[14,888,889],{},"re"," uses ",[14,892,893],{},"\\1",[14,895,896],{},"\\2"," (or ",[14,899,900],{},"\\g\u003C1>","):",[46,903,905],{"className":95,"code":904,"language":97,"meta":51,"style":51},"swapped = df[\"Note\"].str.replace(r\"INV-(\\d{4})-(\\d+)\", r\"\\2\u002F\\1\", regex=True)\nprint(swapped.to_string())\n\nmasked = df[\"Note\"].str.replace(r\"\\b[\\w.]+@[\\w.]+\\b\", \"[email removed]\", regex=True)\nmasked = masked.str.replace(r\"\\b\\d{4} \\d{3} \\d{4}\\b\", \"[phone removed]\", regex=True)\nprint(masked.iloc[[3, 5]].to_string())\n",[14,906,907,967,974,978,1038,1085],{"__ignoreMap":51},[55,908,909,912,914,916,918,921,923,925,928,930,932,934,936,938,940,942,944,946,948,950,953,955,957,959,961,963,965],{"class":57,"line":58},[55,910,911],{"class":108},"swapped ",[55,913,131],{"class":104},[55,915,201],{"class":108},[55,917,204],{"class":65},[55,919,920],{"class":108},"].str.replace(",[55,922,349],{"class":104},[55,924,167],{"class":65},[55,926,927],{"class":149},"(\\d",[55,929,173],{"class":104},[55,931,371],{"class":149},[55,933,176],{"class":65},[55,935,927],{"class":149},[55,937,181],{"class":104},[55,939,371],{"class":149},[55,941,352],{"class":65},[55,943,140],{"class":108},[55,945,349],{"class":104},[55,947,352],{"class":65},[55,949,896],{"class":448},[55,951,952],{"class":65},"\u002F",[55,954,893],{"class":448},[55,956,352],{"class":65},[55,958,140],{"class":108},[55,960,210],{"class":143},[55,962,131],{"class":104},[55,964,215],{"class":149},[55,966,153],{"class":108},[55,968,969,971],{"class":57,"line":118},[55,970,258],{"class":149},[55,972,973],{"class":108},"(swapped.to_string())\n",[55,975,976],{"class":57,"line":125},[55,977,122],{"emptyLinePlaceholder":121},[55,979,980,983,985,987,989,991,993,995,998,1001,1004,1006,1009,1012,1014,1016,1018,1021,1023,1025,1028,1030,1032,1034,1036],{"class":57,"line":156},[55,981,982],{"class":108},"masked ",[55,984,131],{"class":104},[55,986,201],{"class":108},[55,988,204],{"class":65},[55,990,920],{"class":108},[55,992,349],{"class":104},[55,994,352],{"class":65},[55,996,997],{"class":149},"\\b[\\w",[55,999,39],{"class":1000},"sSjpA",[55,1002,1003],{"class":149},"]",[55,1005,181],{"class":104},[55,1007,1008],{"class":65},"@",[55,1010,1011],{"class":149},"[\\w",[55,1013,39],{"class":1000},[55,1015,1003],{"class":149},[55,1017,181],{"class":104},[55,1019,1020],{"class":149},"\\b",[55,1022,352],{"class":65},[55,1024,140],{"class":108},[55,1026,1027],{"class":65},"\"[email removed]\"",[55,1029,140],{"class":108},[55,1031,210],{"class":143},[55,1033,131],{"class":104},[55,1035,215],{"class":149},[55,1037,153],{"class":108},[55,1039,1040,1042,1044,1047,1049,1051,1054,1056,1059,1062,1064,1066,1068,1070,1072,1075,1077,1079,1081,1083],{"class":57,"line":187},[55,1041,982],{"class":108},[55,1043,131],{"class":104},[55,1045,1046],{"class":108}," masked.str.replace(",[55,1048,349],{"class":104},[55,1050,352],{"class":65},[55,1052,1053],{"class":149},"\\b\\d",[55,1055,173],{"class":104},[55,1057,1058],{"class":149}," \\d",[55,1060,1061],{"class":104},"{3}",[55,1063,1058],{"class":149},[55,1065,173],{"class":104},[55,1067,1020],{"class":149},[55,1069,352],{"class":65},[55,1071,140],{"class":108},[55,1073,1074],{"class":65},"\"[phone removed]\"",[55,1076,140],{"class":108},[55,1078,210],{"class":143},[55,1080,131],{"class":104},[55,1082,215],{"class":149},[55,1084,153],{"class":108},[55,1086,1087,1089,1092,1095,1097,1100],{"class":57,"line":220},[55,1088,258],{"class":149},[55,1090,1091],{"class":108},"(masked.iloc[[",[55,1093,1094],{"class":149},"3",[55,1096,140],{"class":108},[55,1098,1099],{"class":149},"5",[55,1101,270],{"class":108},[10,1103,1104,1105,1107,1108,1110,1111,1113,1114,39],{},"Copying a replacement from an Excel formula without changing ",[14,1106,882],{}," to ",[14,1109,893],{}," inserts the literal text ",[14,1112,882],{}," — no error, just wrong output, so it is worth a test. The second example is a typical clean-up job: masking email addresses and phone numbers before a file is shared, as in ",[35,1115,1117],{"href":1116},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fanonymise-personal-data-in-an-excel-file-with-python\u002F","Anonymise Personal Data in an Excel File with Python",[41,1119,1121],{"id":1120},"step-5-replacing-only-the-nth-occurrence","Step 5 — Replacing only the nth occurrence",[10,1123,1124,1126,1127,1130,1131,1134,1135,1138,1139,1143],{},[14,1125,24],{},"'s fourth argument, ",[14,1128,1129],{},"occurrence",", replaces only the nth match (0, the default, replaces all; negative numbers count from the end). ",[14,1132,1133],{},"str.replace"," has ",[14,1136,1137],{},"n",", but that limits the ",[1140,1141,1142],"em",{},"number"," of replacements from the start, so it covers only the first occurrence. For \"the second\" or \"the last\", use a function:",[46,1145,1147],{"className":95,"code":1146,"language":97,"meta":51,"style":51},"def replace_occurrence(text, pattern, repl, occurrence, flags=0):\n    matches = list(re.finditer(pattern, text, flags))\n    if not matches or occurrence == 0:\n        return re.sub(pattern, repl, text, flags=flags)\n    index = occurrence - 1 if occurrence > 0 else len(matches) + occurrence\n    if not 0 \u003C= index \u003C len(matches):\n        return text\n    m = matches[index]\n    return text[:m.start()] + m.expand(repl) + text[m.end():]\n\nnote = df.loc[1, \"Note\"]\nprint(replace_occurrence(note, r\"INV-\\d{4}-\\d+\", \"\u003Csecond>\", 2, re.I))\nprint(replace_occurrence(note, r\"INV-\\d{4}-\\d+\", \"\u003Clast>\", -1, re.I))\nprint(df[\"Note\"].map(lambda t: replace_occurrence(t, r\"INV-\\d{4}-\\d+\", \"\u003Cfirst>\", 1, re.I)).iloc[1])\n",[14,1148,1149,1168,1181,1207,1222,1260,1282,1289,1300,1319,1324,1344,1380,1416],{"__ignoreMap":51},[55,1150,1151,1154,1158,1161,1163,1165],{"class":57,"line":58},[55,1152,1153],{"class":104},"def",[55,1155,1157],{"class":1156},"s_Opv"," replace_occurrence",[55,1159,1160],{"class":108},"(text, pattern, repl, occurrence, flags",[55,1162,131],{"class":104},[55,1164,392],{"class":149},[55,1166,1167],{"class":108},"):\n",[55,1169,1170,1173,1175,1178],{"class":57,"line":118},[55,1171,1172],{"class":108},"    matches ",[55,1174,131],{"class":104},[55,1176,1177],{"class":149}," list",[55,1179,1180],{"class":108},"(re.finditer(pattern, text, flags))\n",[55,1182,1183,1186,1189,1192,1195,1198,1201,1204],{"class":57,"line":125},[55,1184,1185],{"class":104},"    if",[55,1187,1188],{"class":104}," not",[55,1190,1191],{"class":108}," matches ",[55,1193,1194],{"class":104},"or",[55,1196,1197],{"class":108}," occurrence ",[55,1199,1200],{"class":104},"==",[55,1202,1203],{"class":149}," 0",[55,1205,1206],{"class":108},":\n",[55,1208,1209,1212,1215,1217,1219],{"class":57,"line":156},[55,1210,1211],{"class":104},"        return",[55,1213,1214],{"class":108}," re.sub(pattern, repl, text, ",[55,1216,387],{"class":143},[55,1218,131],{"class":104},[55,1220,1221],{"class":108},"flags)\n",[55,1223,1224,1227,1229,1231,1233,1236,1239,1241,1244,1246,1249,1252,1255,1257],{"class":57,"line":187},[55,1225,1226],{"class":108},"    index ",[55,1228,131],{"class":104},[55,1230,1197],{"class":108},[55,1232,176],{"class":104},[55,1234,1235],{"class":149}," 1",[55,1237,1238],{"class":104}," if",[55,1240,1197],{"class":108},[55,1242,1243],{"class":104},">",[55,1245,1203],{"class":149},[55,1247,1248],{"class":104}," else",[55,1250,1251],{"class":149}," len",[55,1253,1254],{"class":108},"(matches) ",[55,1256,181],{"class":104},[55,1258,1259],{"class":108}," occurrence\n",[55,1261,1262,1264,1266,1268,1271,1274,1277,1279],{"class":57,"line":220},[55,1263,1185],{"class":104},[55,1265,1188],{"class":104},[55,1267,1203],{"class":149},[55,1269,1270],{"class":104}," \u003C=",[55,1272,1273],{"class":108}," index ",[55,1275,1276],{"class":104},"\u003C",[55,1278,1251],{"class":149},[55,1280,1281],{"class":108},"(matches):\n",[55,1283,1284,1286],{"class":57,"line":255},[55,1285,1211],{"class":104},[55,1287,1288],{"class":108}," text\n",[55,1290,1292,1295,1297],{"class":57,"line":1291},8,[55,1293,1294],{"class":108},"    m ",[55,1296,131],{"class":104},[55,1298,1299],{"class":108}," matches[index]\n",[55,1301,1303,1306,1309,1311,1314,1316],{"class":57,"line":1302},9,[55,1304,1305],{"class":104},"    return",[55,1307,1308],{"class":108}," text[:m.start()] ",[55,1310,181],{"class":104},[55,1312,1313],{"class":108}," m.expand(repl) ",[55,1315,181],{"class":104},[55,1317,1318],{"class":108}," text[m.end():]\n",[55,1320,1322],{"class":57,"line":1321},10,[55,1323,122],{"emptyLinePlaceholder":121},[55,1325,1327,1330,1332,1335,1337,1339,1341],{"class":57,"line":1326},11,[55,1328,1329],{"class":108},"note ",[55,1331,131],{"class":104},[55,1333,1334],{"class":108}," df.loc[",[55,1336,499],{"class":149},[55,1338,140],{"class":108},[55,1340,204],{"class":65},[55,1342,1343],{"class":108},"]\n",[55,1345,1347,1349,1352,1354,1356,1358,1360,1362,1364,1366,1368,1370,1373,1375,1377],{"class":57,"line":1346},12,[55,1348,258],{"class":149},[55,1350,1351],{"class":108},"(replace_occurrence(note, ",[55,1353,349],{"class":104},[55,1355,167],{"class":65},[55,1357,170],{"class":149},[55,1359,173],{"class":104},[55,1361,176],{"class":65},[55,1363,170],{"class":149},[55,1365,181],{"class":104},[55,1367,352],{"class":65},[55,1369,140],{"class":108},[55,1371,1372],{"class":65},"\"\u003Csecond>\"",[55,1374,140],{"class":108},[55,1376,650],{"class":149},[55,1378,1379],{"class":108},", re.I))\n",[55,1381,1383,1385,1387,1389,1391,1393,1395,1397,1399,1401,1403,1405,1408,1410,1412,1414],{"class":57,"line":1382},13,[55,1384,258],{"class":149},[55,1386,1351],{"class":108},[55,1388,349],{"class":104},[55,1390,167],{"class":65},[55,1392,170],{"class":149},[55,1394,173],{"class":104},[55,1396,176],{"class":65},[55,1398,170],{"class":149},[55,1400,181],{"class":104},[55,1402,352],{"class":65},[55,1404,140],{"class":108},[55,1406,1407],{"class":65},"\"\u003Clast>\"",[55,1409,140],{"class":108},[55,1411,176],{"class":104},[55,1413,499],{"class":149},[55,1415,1379],{"class":108},[55,1417,1419,1421,1424,1426,1429,1431,1434,1436,1438,1440,1442,1444,1446,1448,1450,1452,1455,1457,1459,1462,1464],{"class":57,"line":1418},14,[55,1420,258],{"class":149},[55,1422,1423],{"class":108},"(df[",[55,1425,204],{"class":65},[55,1427,1428],{"class":108},"].map(",[55,1430,797],{"class":104},[55,1432,1433],{"class":108}," t: replace_occurrence(t, ",[55,1435,349],{"class":104},[55,1437,167],{"class":65},[55,1439,170],{"class":149},[55,1441,173],{"class":104},[55,1443,176],{"class":65},[55,1445,170],{"class":149},[55,1447,181],{"class":104},[55,1449,352],{"class":65},[55,1451,140],{"class":108},[55,1453,1454],{"class":65},"\"\u003Cfirst>\"",[55,1456,140],{"class":108},[55,1458,499],{"class":149},[55,1460,1461],{"class":108},", re.I)).iloc[",[55,1463,499],{"class":149},[55,1465,1466],{"class":108},"])\n",[10,1468,1469,1472,1473,1475,1476,39],{},[14,1470,1471],{},"m.expand(repl)"," applies ",[14,1474,893],{},"-style group references in the replacement, so the helper accepts the same replacement strings as ",[14,1477,1133],{},[536,1479,545,1484,545,1487,545,1490,545,1493,545,1497,545,1501,545,1506,545,1509,545,1512,545,1517,545,1522,545,1524,545,1529,545,1531,545,1534,545,1537,545,1541,545,1543,545,1547,545,1549,545,1552,545,1554,545,1558,545,1560,545,1564,545,1566,545,1569,545,1572,545,1576,545,1578,545,1582,545,1584,545,1587,545,1590,545,1593,545,1595,545,1599,545,1601,545,1604,545,1607,545,1611,545,1613,545,1617,545,1619],{"viewBox":1480,"role":539,"ariaLabelledBy":1481,"xmlns":543,"style":544},"4 0 752 276",[1482,1483],"rx-map-t","rx-map-d",[547,1485,1486],{"id":1482},"Excel regex functions and their pandas equivalents",[551,1488,1489],{"id":1483},"REGEXTEST maps to str.contains, REGEXEXTRACT mode 0 to str.extract, mode 1 to str.findall, mode 2 to str.extract with several groups, REGEXREPLACE to str.replace, and the occurrence argument to a helper using re.finditer. Case sensitivity is case=False or flags=re.I, group references change from $1 to \\1, and no match gives NaN instead of",[555,1491],{"x":557,"y":392,"width":558,"height":1492,"fill":560},"276",[555,1494],{"x":563,"y":1495,"width":1496,"height":564,"rx":392,"fill":560,"stroke":567,"style":568},"16.0","186.6",[555,1498],{"x":1499,"y":1495,"width":1500,"height":564,"rx":392,"fill":595,"stroke":596,"style":704},"206.6","266.7",[570,1502,610],{"x":1503,"y":1504,"style":1505},"339.9","41.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[555,1507],{"x":1508,"y":1495,"width":1500,"height":564,"rx":392,"fill":595,"stroke":596,"style":704},"473.3",[570,1510,682],{"x":1511,"y":1504,"style":1505},"606.6",[555,1513],{"x":563,"y":1514,"width":1496,"height":1515,"rx":392,"fill":1516,"stroke":567,"style":568},"56.0","34.0","#e7ebef",[570,1518,16],{"x":1519,"y":1520,"style":1521},"32.0","78.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:start",[555,1523],{"x":1499,"y":1514,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1525,1528],{"x":1503,"y":1526,"style":1527},"77.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","str.contains(p, regex=True)",[555,1530],{"x":1508,"y":1514,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1532,1533],{"x":1511,"y":1526,"style":1527},"case=False for 1",[555,1535],{"x":563,"y":1536,"width":1496,"height":1515,"rx":392,"fill":1516,"stroke":567,"style":568},"90.0",[570,1538,1540],{"x":1519,"y":1539,"style":1521},"112.0","REGEXEXTRACT mode 0",[555,1542],{"x":1499,"y":1536,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1544,1546],{"x":1503,"y":1545,"style":1527},"111.0","str.extract(\"(p)\")",[555,1548],{"x":1508,"y":1536,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1550,1551],{"x":1511,"y":1545,"style":1527},"first match",[555,1553],{"x":563,"y":646,"width":1496,"height":1515,"rx":392,"fill":1516,"stroke":567,"style":568},[570,1555,1557],{"x":1519,"y":1556,"style":1521},"146.0","REGEXEXTRACT mode 1",[555,1559],{"x":1499,"y":646,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1561,1563],{"x":1503,"y":1562,"style":1527},"145.0","str.findall(p)",[555,1565],{"x":1508,"y":646,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1567,1568],{"x":1511,"y":1562,"style":1527},"list per row",[555,1570],{"x":563,"y":1571,"width":1496,"height":1515,"rx":392,"fill":1516,"stroke":567,"style":568},"158.0",[570,1573,1575],{"x":1519,"y":1574,"style":1521},"180.0","REGEXEXTRACT mode 2",[555,1577],{"x":1499,"y":1571,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1579,1581],{"x":1503,"y":1580,"style":1527},"179.0","str.extract with groups",[555,1583],{"x":1508,"y":1571,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1585,1586],{"x":1511,"y":1580,"style":1527},"one column per group",[555,1588],{"x":563,"y":1589,"width":1496,"height":1515,"rx":392,"fill":1516,"stroke":567,"style":568},"192.0",[570,1591,24],{"x":1519,"y":1592,"style":1521},"214.0",[555,1594],{"x":1499,"y":1589,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1596,1598],{"x":1503,"y":1597,"style":1527},"213.0","str.replace(p, r, regex=True)",[555,1600],{"x":1508,"y":1589,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1602,1603],{"x":1511,"y":1597,"style":1527},"$1 becomes \\1",[555,1605],{"x":563,"y":1606,"width":1496,"height":1515,"rx":392,"fill":1516,"stroke":567,"style":568},"226.0",[570,1608,1610],{"x":1519,"y":1609,"style":1521},"248.0","occurrence = n",[555,1612],{"x":1499,"y":1606,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1614,1616],{"x":1503,"y":1615,"style":1527},"247.0","re.finditer helper",[555,1618],{"x":1508,"y":1606,"width":1500,"height":1515,"rx":392,"fill":560,"stroke":567,"style":568},[570,1620,1621],{"x":1511,"y":1615,"style":1527},"n counts matches",[41,1623,1625],{"id":1624},"step-6-patterns-that-behave-differently","Step 6 — Patterns that behave differently",[10,1627,1628,1629,1631,1632,140,1634,140,1637,1639],{},"Excel uses the PCRE2 regular-expression library; Python uses its own ",[14,1630,889],{}," module. Everyday patterns — character classes, quantifiers, groups, anchors, ",[14,1633,170],{},[14,1635,1636],{},"\\w",[14,1638,1020],{}," — work the same. A few constructs do not:",[46,1641,1643],{"className":95,"code":1642,"language":97,"meta":51,"style":51},"tests = {\n    r\"\\d+(?=-\\d)\": \"lookahead (both support it)\",\n    r\"(?i)inv\": \"inline case flag (both)\",\n    r\"\\p{Lu}+\": \"Unicode property (PCRE2 only)\",\n}\nfor pat, label in tests.items():\n    try:\n        print(f\"{pat:14} {label:30} -> {re.findall(pat, 'INV-2026-0042 ÄRGER')}\")\n    except re.error as exc:\n        print(f\"{pat:14} {label:30} -> re.error: {exc}\")\n",[14,1644,1645,1655,1684,1703,1727,1732,1744,1751,1805,1818],{"__ignoreMap":51},[55,1646,1647,1650,1652],{"class":57,"line":58},[55,1648,1649],{"class":108},"tests ",[55,1651,131],{"class":104},[55,1653,1654],{"class":108}," {\n",[55,1656,1657,1660,1662,1664,1667,1669,1671,1673,1675,1678,1681],{"class":57,"line":118},[55,1658,1659],{"class":104},"    r",[55,1661,352],{"class":65},[55,1663,170],{"class":149},[55,1665,1666],{"class":104},"+(?=",[55,1668,176],{"class":65},[55,1670,170],{"class":149},[55,1672,371],{"class":104},[55,1674,352],{"class":65},[55,1676,1677],{"class":108},": ",[55,1679,1680],{"class":65},"\"lookahead (both support it)\"",[55,1682,1683],{"class":108},",\n",[55,1685,1686,1688,1690,1693,1696,1698,1701],{"class":57,"line":125},[55,1687,1659],{"class":104},[55,1689,352],{"class":65},[55,1691,1692],{"class":104},"(?i)",[55,1694,1695],{"class":65},"inv\"",[55,1697,1677],{"class":108},[55,1699,1700],{"class":65},"\"inline case flag (both)\"",[55,1702,1683],{"class":108},[55,1704,1705,1707,1709,1713,1716,1718,1720,1722,1725],{"class":57,"line":156},[55,1706,1659],{"class":104},[55,1708,352],{"class":65},[55,1710,1712],{"class":1711},"s_b0D","\\p",[55,1714,1715],{"class":65},"{Lu}",[55,1717,181],{"class":104},[55,1719,352],{"class":65},[55,1721,1677],{"class":108},[55,1723,1724],{"class":65},"\"Unicode property (PCRE2 only)\"",[55,1726,1683],{"class":108},[55,1728,1729],{"class":57,"line":187},[55,1730,1731],{"class":108},"}\n",[55,1733,1734,1736,1739,1741],{"class":57,"line":220},[55,1735,803],{"class":104},[55,1737,1738],{"class":108}," pat, label ",[55,1740,809],{"class":104},[55,1742,1743],{"class":108}," tests.items():\n",[55,1745,1746,1749],{"class":57,"line":255},[55,1747,1748],{"class":104},"    try",[55,1750,1206],{"class":108},[55,1752,1753,1756,1758,1761,1763,1766,1769,1772,1775,1778,1781,1784,1786,1789,1791,1794,1797,1799,1801,1803],{"class":57,"line":1291},[55,1754,1755],{"class":149},"        print",[55,1757,355],{"class":108},[55,1759,1760],{"class":104},"f",[55,1762,352],{"class":65},[55,1764,1765],{"class":1000},"{",[55,1767,1768],{"class":108},"pat",[55,1770,1771],{"class":104},":14",[55,1773,1774],{"class":1000},"}",[55,1776,1777],{"class":1000}," {",[55,1779,1780],{"class":108},"label",[55,1782,1783],{"class":104},":30",[55,1785,1774],{"class":1000},[55,1787,1788],{"class":65}," -> ",[55,1790,1765],{"class":1000},[55,1792,1793],{"class":108},"re.findall(pat, ",[55,1795,1796],{"class":65},"'INV-2026-0042 ÄRGER'",[55,1798,371],{"class":108},[55,1800,1774],{"class":1000},[55,1802,352],{"class":65},[55,1804,153],{"class":108},[55,1806,1807,1810,1813,1815],{"class":57,"line":1302},[55,1808,1809],{"class":104},"    except",[55,1811,1812],{"class":108}," re.error ",[55,1814,112],{"class":104},[55,1816,1817],{"class":108}," exc:\n",[55,1819,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844,1847,1849,1852,1854,1856],{"class":57,"line":1321},[55,1821,1755],{"class":149},[55,1823,355],{"class":108},[55,1825,1760],{"class":104},[55,1827,352],{"class":65},[55,1829,1765],{"class":1000},[55,1831,1768],{"class":108},[55,1833,1771],{"class":104},[55,1835,1774],{"class":1000},[55,1837,1777],{"class":1000},[55,1839,1780],{"class":108},[55,1841,1783],{"class":104},[55,1843,1774],{"class":1000},[55,1845,1846],{"class":65}," -> re.error: ",[55,1848,1765],{"class":1000},[55,1850,1851],{"class":108},"exc",[55,1853,1774],{"class":1000},[55,1855,352],{"class":65},[55,1857,153],{"class":108},[10,1859,1860,1861,1864,1865,1867,1868,1870,1871,719,1873,1875,1876,1879],{},"Unicode property classes such as ",[14,1862,1863],{},"\\p{Lu}"," (upper-case letters in any script) are PCRE2 features that Python's ",[14,1866,889],{}," rejects; the third-party ",[14,1869,210],{}," package supports them with the same API. Python's ",[14,1872,1636],{},[14,1874,170],{}," are Unicode-aware by default, so they also match accented letters and non-ASCII digits — usually desirable, but pass ",[14,1877,1878],{},"flags=re.ASCII"," to mirror patterns that assume ASCII. When translating a formula, run its pattern against a few known inputs in both places before trusting it on the whole column.",[41,1881,1883],{"id":1882},"step-7-match-extracted-codes-against-the-real-list","Step 7 — Match extracted codes against the real list",[10,1885,1886],{},"Extraction is rarely the end of the job. The usual next question is \"which of these notes mention an invoice we do not recognise?\" — a typo, a cancelled invoice, a number from another system. Explode the matches into one row per mention and compare them with the invoice register:",[46,1888,1890],{"className":95,"code":1889,"language":97,"meta":51,"style":51},"register = pd.DataFrame({\"Invoice\": [\"INV-2026-0042\", \"INV-2026-0107\", \"INV-2026-0108\", \"INV-2026-0200\"]})\nmentions = (df.assign(Invoice=normalised)\n              .explode(\"Invoice\")\n              .dropna(subset=[\"Invoice\"])[[\"Invoice\", \"Note\"]])\nchecked = mentions.merge(register.assign(Known=True), on=\"Invoice\", how=\"left\")\nchecked[\"Known\"] = checked[\"Known\"].fillna(False).astype(bool)\nprint(checked[[\"Invoice\", \"Known\"]].to_string(index=False))\n",[14,1891,1892,1929,1947,1956,1983,2022,2052],{"__ignoreMap":51},[55,1893,1894,1897,1899,1902,1905,1908,1911,1913,1916,1918,1921,1923,1926],{"class":57,"line":58},[55,1895,1896],{"class":108},"register ",[55,1898,131],{"class":104},[55,1900,1901],{"class":108}," pd.DataFrame({",[55,1903,1904],{"class":65},"\"Invoice\"",[55,1906,1907],{"class":108},": [",[55,1909,1910],{"class":65},"\"INV-2026-0042\"",[55,1912,140],{"class":108},[55,1914,1915],{"class":65},"\"INV-2026-0107\"",[55,1917,140],{"class":108},[55,1919,1920],{"class":65},"\"INV-2026-0108\"",[55,1922,140],{"class":108},[55,1924,1925],{"class":65},"\"INV-2026-0200\"",[55,1927,1928],{"class":108},"]})\n",[55,1930,1931,1934,1936,1939,1942,1944],{"class":57,"line":118},[55,1932,1933],{"class":108},"mentions ",[55,1935,131],{"class":104},[55,1937,1938],{"class":108}," (df.assign(",[55,1940,1941],{"class":143},"Invoice",[55,1943,131],{"class":104},[55,1945,1946],{"class":108},"normalised)\n",[55,1948,1949,1952,1954],{"class":57,"line":125},[55,1950,1951],{"class":108},"              .explode(",[55,1953,1904],{"class":65},[55,1955,153],{"class":108},[55,1957,1958,1961,1964,1966,1969,1971,1974,1976,1978,1980],{"class":57,"line":156},[55,1959,1960],{"class":108},"              .dropna(",[55,1962,1963],{"class":143},"subset",[55,1965,131],{"class":104},[55,1967,1968],{"class":108},"[",[55,1970,1904],{"class":65},[55,1972,1973],{"class":108},"])[[",[55,1975,1904],{"class":65},[55,1977,140],{"class":108},[55,1979,204],{"class":65},[55,1981,1982],{"class":108},"]])\n",[55,1984,1985,1988,1990,1993,1996,1998,2000,2003,2006,2008,2010,2012,2015,2017,2020],{"class":57,"line":187},[55,1986,1987],{"class":108},"checked ",[55,1989,131],{"class":104},[55,1991,1992],{"class":108}," mentions.merge(register.assign(",[55,1994,1995],{"class":143},"Known",[55,1997,131],{"class":104},[55,1999,215],{"class":149},[55,2001,2002],{"class":108},"), ",[55,2004,2005],{"class":143},"on",[55,2007,131],{"class":104},[55,2009,1904],{"class":65},[55,2011,140],{"class":108},[55,2013,2014],{"class":143},"how",[55,2016,131],{"class":104},[55,2018,2019],{"class":65},"\"left\"",[55,2021,153],{"class":108},[55,2023,2024,2027,2030,2032,2034,2037,2039,2042,2044,2047,2050],{"class":57,"line":220},[55,2025,2026],{"class":108},"checked[",[55,2028,2029],{"class":65},"\"Known\"",[55,2031,196],{"class":108},[55,2033,131],{"class":104},[55,2035,2036],{"class":108}," checked[",[55,2038,2029],{"class":65},[55,2040,2041],{"class":108},"].fillna(",[55,2043,150],{"class":149},[55,2045,2046],{"class":108},").astype(",[55,2048,2049],{"class":149},"bool",[55,2051,153],{"class":108},[55,2053,2054,2056,2059,2061,2063,2065,2068,2071,2073,2075],{"class":57,"line":255},[55,2055,258],{"class":149},[55,2057,2058],{"class":108},"(checked[[",[55,2060,1904],{"class":65},[55,2062,140],{"class":108},[55,2064,2029],{"class":65},[55,2066,2067],{"class":108},"]].to_string(",[55,2069,2070],{"class":143},"index",[55,2072,131],{"class":104},[55,2074,150],{"class":149},[55,2076,2077],{"class":108},"))\n",[536,2079,545,2084,545,2087,545,2090,545,2093,545,2098,545,2109,545,2115,545,2119,545,2124,545,2127,545,2130,545,2137,545,2143,545,2147,545,2151,545,2155,545,2158,545,2161,545,2167,545,2171,545,2175,545,2178,545,2182,545,2185,545,2190,545,2197,545,2202,545,2205],{"viewBox":2080,"role":539,"ariaLabelledBy":2081,"xmlns":543,"style":544},"4 0 752 142",[2082,2083],"rx-pipeline-t","rx-pipeline-d",[547,2085,2086],{"id":2082},"From free-text notes to checked invoice numbers",[551,2088,2089],{"id":2083},"The notes column is searched case-insensitively with findall, the matches are upper-cased, exploded into one row per mention, and merged with the invoice register to flag unknown numbers.",[555,2091],{"x":557,"y":392,"width":558,"height":2092,"fill":560},"142",[555,2094],{"x":563,"y":1495,"width":2095,"height":2096,"rx":2097,"fill":1516,"stroke":567,"style":691},"153.0","110.5","12",[2099,2100,2102],"g",{"transform":2101},"translate(84.5,37.8) scale(1.000)",[2103,2104],"path",{"d":2105,"fill":2106,"stroke":2107,"style":2108},"M6 3h9l4 4v14H6z M15 3v4h4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[570,2110,2114],{"x":2111,"y":2112,"style":2113},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Notes column",[570,2116,2118],{"x":2111,"y":2117,"style":1527},"103.8","free text",[57,2120],{"x1":2121,"y1":2122,"x2":2123,"y2":2122,"stroke":596,"style":691},"178.0","71.2","200.0",[693,2125],{"points":2126,"fill":696},"204.0,71.2 195.0,76.2 195.0,66.2",[555,2128],{"x":2129,"y":1495,"width":2095,"height":2096,"rx":2097,"fill":595,"stroke":596,"style":691},"209.0",[2099,2131,2133],{"transform":2132},"translate(273.5,29.0) scale(1.000)",[2103,2134],{"d":2135,"fill":2106,"stroke":2136,"style":2108},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--brand-strong,#4338ca)",[570,2138,2142],{"x":2139,"y":2140,"style":2141},"285.5","75.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","findall + re.I",[570,2144,2146],{"x":2139,"y":2145,"style":1527},"95.0","every invoice",[570,2148,2150],{"x":2139,"y":2149,"style":1527},"112.5","mentioned",[57,2152],{"x1":2153,"y1":2122,"x2":2154,"y2":2122,"stroke":596,"style":691},"367.0","389.0",[693,2156],{"points":2157,"fill":696},"393.0,71.2 384.0,76.2 384.0,66.2",[555,2159],{"x":2160,"y":1495,"width":2095,"height":2096,"rx":2097,"fill":595,"stroke":596,"style":691},"398.0",[2099,2162,2164],{"transform":2163},"translate(462.5,29.0) scale(1.000)",[2103,2165],{"d":2166,"fill":2106,"stroke":2136,"style":2108},"M4 12h6 M10 12l6-6h4 M10 12l6 6h4 M17 3l3 3-3 3 M17 15l3 3-3 3",[570,2168,2170],{"x":2169,"y":2140,"style":2141},"474.5","Upper-case +",[570,2172,2174],{"x":2169,"y":2173,"style":2141},"93.0","explode",[570,2176,2177],{"x":2169,"y":2149,"style":1527},"one row per mention",[57,2179],{"x1":2180,"y1":2122,"x2":2181,"y2":2122,"stroke":596,"style":691},"556.0","578.0",[693,2183],{"points":2184,"fill":696},"582.0,71.2 573.0,76.2 573.0,66.2",[555,2186],{"x":2187,"y":1495,"width":2095,"height":2096,"rx":2097,"fill":2188,"stroke":2189,"style":691},"587.0","#d9f4f1","var(--teal,#0f9488)",[2099,2191,2193],{"transform":2192},"translate(651.5,29.0) scale(1.000)",[2103,2194],{"d":2195,"fill":2106,"stroke":2196,"style":2108},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[570,2198,2201],{"x":2199,"y":2140,"style":2200},"663.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Merge with",[570,2203,2204],{"x":2199,"y":2173,"style":2200},"register",[570,2206,2207],{"x":2199,"y":2149,"style":1527},"flag unknown numbers",[10,2209,2210,2211,2214,2215,2219],{},"Four of the five mentions are in the register; ",[14,2212,2213],{},"INV-2025-0991"," is not, and that row is the one to send back for checking. Doing this as an exploded table rather than inside a formula keeps every mention visible — a note that names two invoices produces two rows, so neither can hide behind the other. The register comparison is an ordinary left merge; ",[35,2216,2218],{"href":2217},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Ffind-rows-in-one-excel-file-missing-from-another\u002F","Find Rows in One Excel File Missing from Another"," covers the variations.",[41,2221,2223],{"id":2222},"common-pitfalls","Common pitfalls",[2225,2226,2227,2246,2263,2281,2293],"ul",{},[2228,2229,2230,2236,2237,2239,2240,2242,2243,2245],"li",{},[2231,2232,2233,2235],"strong",{},[14,2234,882],{}," in replacements."," Python needs ",[14,2238,893],{}," or ",[14,2241,900],{},"; ",[14,2244,882],{}," is inserted literally.",[2228,2247,2248,2251,2252,2255,2256,2259,2260,39],{},[2231,2249,2250],{},"Forgetting raw strings."," ",[14,2253,2254],{},"\"\\d\""," works by luck, ",[14,2257,2258],{},"\"\\b\""," becomes a backspace character; write ",[14,2261,2262],{},"r\"\\b\"",[2228,2264,2265,2270,2271,2273,2274,2276,2277,2280],{},[2231,2266,2267,2269],{},[14,2268,299],{}," in the column."," String methods return ",[14,2272,299],{}," for those rows; use ",[14,2275,316],{}," with ",[14,2278,2279],{},"contains",", or fill first.",[2228,2282,2283,2288,2289,2292],{},[2231,2284,2285,2287],{},[14,2286,507],{}," without a group."," It raises ",[14,2290,2291],{},"ValueError: pattern contains no capture groups","; wrap the pattern in parentheses.",[2228,2294,2295,2298],{},[2231,2296,2297],{},"Case defaults."," Excel's default is case-sensitive, like pandas; ignoring case is opt-in on both sides.",[41,2300,2302],{"id":2301},"conclusion","Conclusion",[10,2304,2305,2307,2308,140,2310,2307,2312,2314,2315,2317,2318,2307,2320,2323,2324,2326,2327,2329,2330,2239,2332,2334,2335,2338,2339,2341,2342,2344],{},[14,2306,16],{}," is ",[14,2309,306],{},[14,2311,20],{},[14,2313,507],{}," for the first match or capture groups and ",[14,2316,511],{}," for every match, and ",[14,2319,24],{},[14,2321,2322],{},"str.replace(regex=True)"," — with ",[14,2325,882],{}," rewritten as ",[14,2328,893],{},", case-insensitivity passed as ",[14,2331,284],{},[14,2333,861],{},", and a small ",[14,2336,2337],{},"re.finditer"," helper for replacing only the nth occurrence. Use raw strings, handle ",[14,2340,299],{}," rows explicitly, and test patterns that rely on PCRE2-only features, which Python's ",[14,2343,889],{}," does not support.",[41,2346,2348],{"id":2347},"frequently-asked-questions","Frequently asked questions",[10,2350,2351,2354],{},[2231,2352,2353],{},"What is the pandas equivalent of Excel REGEXTEST?","\nSeries.str.contains(pattern, regex=True), which returns True or False per row. Pass case=False to ignore case, like REGEXTEST's case_sensitivity argument of 1, and na=False if the column contains missing values.",[10,2356,2357,2360],{},[2231,2358,2359],{},"How do I get all regex matches from a column like REGEXEXTRACT with return mode 1?","\nUse Series.str.findall(pattern), which returns a list of every match per row. Add flags=re.IGNORECASE for case-insensitive matching and explode the result to get one match per row.",[10,2362,2363,2366],{},[2231,2364,2365],{},"Why does my REGEXREPLACE pattern insert $1 literally in pandas?","\nExcel writes group references as $1 and $2, but Python's re module uses \\1 and \\2 or \\g\u003C1>. Change the replacement string to r\"\\2\u002F\\1\".",[10,2368,2369,2372],{},[2231,2370,2371],{},"How do I replace only the second match in a string with pandas?","\nstr.replace can only limit replacements from the start with n. Use re.finditer to find all matches and rebuild the string around the chosen one, as the replace_occurrence helper on this page does.",[41,2374,2376],{"id":2375},"related","Related",[2225,2378,2379,2385,2392,2399],{},[2228,2380,2381,2382,2384],{},"Up one level: ",[35,2383,38],{"href":37}," — Excel functions translated to pandas.",[2228,2386,2387,2391],{},[35,2388,2390],{"href":2389},"\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"," — practical extraction recipes.",[2228,2393,2394,2398],{},[35,2395,2397],{"href":2396},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Ffind-search-and-substitute-equivalents-in-pandas\u002F","FIND, SEARCH and SUBSTITUTE Equivalents in pandas"," — the non-regex text functions.",[2228,2400,2401,2405],{},[35,2402,2404],{"href":2403},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-email-and-phone-formats-in-excel-data\u002F","Validate Email and Phone Formats in Excel Data"," — patterns for validation.",[2407,2408,2409],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sx1sP, html code.shiki .sx1sP{--shiki-default:#22863A;--shiki-dark:#72F088}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}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}",{"title":51,"searchDepth":118,"depth":118,"links":2411},[2412,2413,2414,2415,2416,2417,2418,2419,2420,2421,2422,2423],{"id":43,"depth":118,"text":44},{"id":85,"depth":118,"text":86},{"id":320,"depth":118,"text":321},{"id":713,"depth":118,"text":714},{"id":872,"depth":118,"text":873},{"id":1120,"depth":118,"text":1121},{"id":1624,"depth":118,"text":1625},{"id":1882,"depth":118,"text":1883},{"id":2222,"depth":118,"text":2223},{"id":2301,"depth":118,"text":2302},{"id":2347,"depth":118,"text":2348},{"id":2375,"depth":118,"text":2376},"2026-10-02","Excel's new regular-expression functions map neatly onto pandas' string methods: REGEXTEST to str.contains, REGEXEXTRACT's three return modes to str.extract, str.findall and capture groups, and REGEXREPLACE to str.replace — with the differences that bite: $1 versus \\1 in replacements, the case-sensitivity argument, replacing only the nth match, #N\u002FA versus NaN, and patterns that behave differently in Python.","md",[2428,2430,2432,2434],{"q":2353,"a":2429},"Series.str.contains(pattern, regex=True), which returns True or False per row. Pass case=False to ignore case, like REGEXTEST's case_sensitivity argument of 1, and na=False if the column contains missing values.",{"q":2359,"a":2431},"Use Series.str.findall(pattern), which returns a list of every match per row. Add flags=re.IGNORECASE for case-insensitive matching and explode the result to get one match per row.",{"q":2365,"a":2433},"Excel writes group references as $1 and $2, but Python's re module uses \\1 and \\2 or \\g\u003C1>. Change the replacement string to r\"\\2\u002F\\1\".",{"q":2371,"a":2435},"str.replace can only limit replacements from the start with n. Use re.finditer to find all matches and rebuild the string around the chosen one, as the replace_occurrence helper on this page does.","regexextract regextest and regexreplace equivalents in pandas",null,{"breadcrumb":2439},[2440,2442,2445],{"name":2441,"item":952},"Home",{"name":2443,"item":2444},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":38,"item":37},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas",{"title":2448,"description":2449},"Excel REGEXTEST, REGEXEXTRACT, REGEXREPLACE in pandas","pandas equivalents of Excel REGEXTEST (str.contains), REGEXEXTRACT return modes 0, 1 and 2 (str.extract, findall), REGEXREPLACE (str.replace with \\1, occurrence), case_sensitivity, no-match results and Python vs PCRE2 pattern differences.","regexextract-regextest-and-regexreplace-equivalents-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fregexextract-regextest-and-regexreplace-equivalents-in-pandas\u002Findex",[2453,2454],"formula-equivalents","data-cleaning","how-to","ssdyoKbdALEuzqDMR63t2MJ4vw0BW9RgQS27baL6x48",[2458,2462],{"title":2459,"path":2460,"stem":2461,"children":-1},"RANK and PERCENTILE Formulas in pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Frank-and-percentile-formulas-in-pandas\u002Findex",{"title":2463,"path":2464,"stem":2465,"children":-1},"Replace Excel Solver with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Freplace-excel-solver-with-python","advanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Freplace-excel-solver-with-python\u002Findex",[2467,2469],{"path":2468,"title":38},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas",{"path":2470,"title":2443},"\u002Fadvanced-data-transformation-and-cleaning",[],[2473,2476,2478,2481,2484,2487],{"path":2474,"title":2475,"kind":2455},"\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":2477,"title":2390,"kind":2455},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fextract-numbers-and-codes-from-text-with-pandas",{"path":2479,"title":2480,"kind":2455},"\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":2482,"title":2483,"kind":2455},"\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":2485,"title":2486,"kind":2455},"\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":2488,"title":2489,"kind":2455},"\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",1790972088466]