[{"data":1,"prerenderedAt":2093},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python":2059,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python":2068,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python":2073,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python":2074},{"id":4,"title":5,"body":6,"dateModified":2025,"datePublished":2025,"description":2026,"extension":2027,"faq":2028,"keyword":2037,"kind":2038,"meta":2039,"navigation":133,"path":2049,"seo":2050,"slug":2052,"stem":2053,"topics":2054,"type":2057,"__hash__":2058},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python\u002Findex.md","Standardise Phone Numbers in Excel Data with Python",{"type":7,"value":8,"toc":2013},"minimark",[9,58,63,94,101,105,237,263,267,809,864,1153,1157,1160,1463,1483,1658,1662,1668,1672,1675,1737,1746,1888,1892,1935,1939,1948,1952,1958,1964,1970,1976,1980,2009],[10,11,12,13,17,18,17,21,17,24,27,28,31,32,35,36,39,40,43,44,47,48,51,52,57],"p",{},"A phone number column in a customer or supplier spreadsheet is usually a record of how many different people typed into it: ",[14,15,16],"code",{},"07911 123456",", ",[14,19,20],{},"+44 (0)20 7946 0958",[14,22,23],{},"(212) 555-0187",[14,25,26],{},"0049 30 901820",", sometimes a fax number, sometimes ",[14,29,30],{},"n\u002Fa",", and occasionally ",[14,33,34],{},"7911123456.0",", a number Excel decided was a number. Any system that sends text messages, dials out or matches customers by phone needs one format, and a person reading the sheet wants a consistent, readable one. Regular expressions get part of the way, but phone numbering rules differ by country — the ",[14,37,38],{},"(0)"," in a UK number must be dropped after ",[14,41,42],{},"+44",", a German ",[14,45,46],{},"0"," trunk prefix likewise, North American numbers have no trunk prefix at all — and the ",[14,49,50],{},"phonenumbers"," library, a port of Google's libphonenumber, encodes those rules for every country. This page reads the column safely as text, parses each number with a default country per row, writes E.164 for systems and international format for people, flags invalid and suspicious numbers for review, and writes the results back as text. It belongs to ",[53,54,56],"a",{"href":55},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002F","Cleaning Excel Data with pandas",".",[59,60,62],"h2",{"id":61},"prerequisites","Prerequisites",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl phonenumbers\n","bash","",[14,71,72],{"__ignoreMap":69},[73,74,77,81,85,88,91],"span",{"class":75,"line":76},"line",1,[73,78,80],{"class":79},"sMTad","pip",[73,82,84],{"class":83},"srMev"," install",[73,86,87],{"class":83}," pandas",[73,89,90],{"class":83}," openpyxl",[73,92,93],{"class":83}," phonenumbers\n",[10,95,96,97,100],{},"The sample is a customer list with a country code per customer and a phone column in a dozen styles, including a number stored as a real number in Excel and a placeholder. The country column matters: ",[14,98,99],{},"030 901820"," is a Berlin number for a German customer and meaningless without that context.",[59,102,104],{"id":103},"step-1-read-the-column-as-text","Step 1 — Read the column as text",[64,106,110],{"className":107,"code":108,"language":109,"meta":69,"style":69},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nraw = pd.read_excel(\"customers.xlsx\")\nprint(raw[\"Phone\"].map(repr).tolist())\n\ncustomers = pd.read_excel(\"customers.xlsx\", dtype={\"Phone\": str}, keep_default_na=False)\nprint(customers[\"Phone\"].tolist())\n","python",[14,111,112,128,135,153,175,180,224],{"__ignoreMap":69},[73,113,114,118,122,125],{"class":75,"line":76},[73,115,117],{"class":116},"s-kum","import",[73,119,121],{"class":120},"skGVy"," pandas ",[73,123,124],{"class":116},"as",[73,126,127],{"class":120}," pd\n",[73,129,131],{"class":75,"line":130},2,[73,132,134],{"emptyLinePlaceholder":133},true,"\n",[73,136,138,141,144,147,150],{"class":75,"line":137},3,[73,139,140],{"class":120},"raw ",[73,142,143],{"class":116},"=",[73,145,146],{"class":120}," pd.read_excel(",[73,148,149],{"class":83},"\"customers.xlsx\"",[73,151,152],{"class":120},")\n",[73,154,156,160,163,166,169,172],{"class":75,"line":155},4,[73,157,159],{"class":158},"sP0c6","print",[73,161,162],{"class":120},"(raw[",[73,164,165],{"class":83},"\"Phone\"",[73,167,168],{"class":120},"].map(",[73,170,171],{"class":158},"repr",[73,173,174],{"class":120},").tolist())\n",[73,176,178],{"class":75,"line":177},5,[73,179,134],{"emptyLinePlaceholder":133},[73,181,183,186,188,190,192,194,198,200,203,205,208,211,214,217,219,222],{"class":75,"line":182},6,[73,184,185],{"class":120},"customers ",[73,187,143],{"class":116},[73,189,146],{"class":120},[73,191,149],{"class":83},[73,193,17],{"class":120},[73,195,197],{"class":196},"sa561","dtype",[73,199,143],{"class":116},[73,201,202],{"class":120},"{",[73,204,165],{"class":83},[73,206,207],{"class":120},": ",[73,209,210],{"class":158},"str",[73,212,213],{"class":120},"}, ",[73,215,216],{"class":196},"keep_default_na",[73,218,143],{"class":116},[73,220,221],{"class":158},"False",[73,223,152],{"class":120},[73,225,227,229,232,234],{"class":75,"line":226},7,[73,228,159],{"class":158},[73,230,231],{"class":120},"(customers[",[73,233,165],{"class":83},[73,235,236],{"class":120},"].tolist())\n",[10,238,239,240,243,244,247,248,251,252,255,256,258,259,57],{},"Read with default settings, the column is a mix: Harbour Print's phone comes back as the integer 7911123457 — Excel stored it as a number, its leading zero long gone — and Corex's \"n\u002Fa\" has already been turned into ",[14,241,242],{},"NaN",". Reading with ",[14,245,246],{},"dtype={\"Phone\": str}"," keeps every value as text, and ",[14,249,250],{},"keep_default_na=False"," stops \"n\u002Fa\" from becoming a missing value before we can see it. The number that Excel already mangled is still mangled — ",[14,253,254],{},"7911123457"," without its ",[14,257,46],{}," — but now it is visible and can be repaired in the next step. Keeping leading zeros in general is covered in ",[53,260,262],{"href":261},"\u002Fgetting-started-with-python-excel-automation\u002Freading-excel-files-with-pandas\u002Fkeep-leading-zeros-when-reading-excel-with-pandas\u002F","Keep Leading Zeros When Reading Excel with pandas",[59,264,266],{"id":265},"step-2-parse-with-a-default-country-per-row","Step 2 — Parse with a default country per row",[64,268,270],{"className":107,"code":269,"language":109,"meta":69,"style":69},"import re\nimport phonenumbers\nfrom phonenumbers import PhoneNumberFormat, PhoneNumberType, NumberParseException\n\nTYPES = {PhoneNumberType.MOBILE: \"mobile\", PhoneNumberType.FIXED_LINE: \"landline\",\n         PhoneNumberType.FIXED_LINE_OR_MOBILE: \"landline or mobile\", PhoneNumberType.TOLL_FREE: \"toll free\"}\n\ndef standardise(raw, country):\n    text = str(raw).strip()\n    if text.lower() in {\"\", \"n\u002Fa\", \"na\", \"none\", \"-\"}:\n        return None, None, None, \"missing\"\n    text = re.sub(r\"\\.0$\", \"\", text)                       # 7911123457.0 from a float cell\n    try:\n        number = phonenumbers.parse(text, country)\n    except NumberParseException as e:\n        return None, None, None, f\"unparseable: {e.error_type}\"\n    if not phonenumbers.is_valid_number(number):\n        return None, None, None, \"invalid for \" + country\n    return (phonenumbers.format_number(number, PhoneNumberFormat.E164),\n            phonenumbers.format_number(number, PhoneNumberFormat.INTERNATIONAL),\n            TYPES.get(phonenumbers.number_type(number), \"other\"),\n            \"ok\" + (f\" (ext {number.extension})\" if number.extension else \"\"))\n\nout = customers.apply(lambda r: pd.Series(standardise(r[\"Phone\"], r[\"Country\"]),\n                                          index=[\"E164\", \"International\", \"Type\", \"Status\"]), axis=1)\nresult = pd.concat([customers, out], axis=1)\nprint(result[[\"Customer\", \"Phone\", \"E164\", \"Type\", \"Status\"]].to_string(index=False))\n",[14,271,272,279,285,298,302,335,361,365,378,392,433,456,494,503,514,528,563,574,600,609,621,635,676,681,709,751,770],{"__ignoreMap":69},[73,273,274,276],{"class":75,"line":76},[73,275,117],{"class":116},[73,277,278],{"class":120}," re\n",[73,280,281,283],{"class":75,"line":130},[73,282,117],{"class":116},[73,284,93],{"class":120},[73,286,287,290,293,295],{"class":75,"line":137},[73,288,289],{"class":116},"from",[73,291,292],{"class":120}," phonenumbers ",[73,294,117],{"class":116},[73,296,297],{"class":120}," PhoneNumberFormat, PhoneNumberType, NumberParseException\n",[73,299,300],{"class":75,"line":155},[73,301,134],{"emptyLinePlaceholder":133},[73,303,304,307,310,313,316,318,321,324,327,329,332],{"class":75,"line":177},[73,305,306],{"class":158},"TYPES",[73,308,309],{"class":116}," =",[73,311,312],{"class":120}," {PhoneNumberType.",[73,314,315],{"class":158},"MOBILE",[73,317,207],{"class":120},[73,319,320],{"class":83},"\"mobile\"",[73,322,323],{"class":120},", PhoneNumberType.",[73,325,326],{"class":158},"FIXED_LINE",[73,328,207],{"class":120},[73,330,331],{"class":83},"\"landline\"",[73,333,334],{"class":120},",\n",[73,336,337,340,343,345,348,350,353,355,358],{"class":75,"line":182},[73,338,339],{"class":120},"         PhoneNumberType.",[73,341,342],{"class":158},"FIXED_LINE_OR_MOBILE",[73,344,207],{"class":120},[73,346,347],{"class":83},"\"landline or mobile\"",[73,349,323],{"class":120},[73,351,352],{"class":158},"TOLL_FREE",[73,354,207],{"class":120},[73,356,357],{"class":83},"\"toll free\"",[73,359,360],{"class":120},"}\n",[73,362,363],{"class":75,"line":226},[73,364,134],{"emptyLinePlaceholder":133},[73,366,368,371,375],{"class":75,"line":367},8,[73,369,370],{"class":116},"def",[73,372,374],{"class":373},"s_Opv"," standardise",[73,376,377],{"class":120},"(raw, country):\n",[73,379,381,384,386,389],{"class":75,"line":380},9,[73,382,383],{"class":120},"    text ",[73,385,143],{"class":116},[73,387,388],{"class":158}," str",[73,390,391],{"class":120},"(raw).strip()\n",[73,393,395,398,401,404,407,410,412,415,417,420,422,425,427,430],{"class":75,"line":394},10,[73,396,397],{"class":116},"    if",[73,399,400],{"class":120}," text.lower() ",[73,402,403],{"class":116},"in",[73,405,406],{"class":120}," {",[73,408,409],{"class":83},"\"\"",[73,411,17],{"class":120},[73,413,414],{"class":83},"\"n\u002Fa\"",[73,416,17],{"class":120},[73,418,419],{"class":83},"\"na\"",[73,421,17],{"class":120},[73,423,424],{"class":83},"\"none\"",[73,426,17],{"class":120},[73,428,429],{"class":83},"\"-\"",[73,431,432],{"class":120},"}:\n",[73,434,436,439,442,444,447,449,451,453],{"class":75,"line":435},11,[73,437,438],{"class":116},"        return",[73,440,441],{"class":158}," None",[73,443,17],{"class":120},[73,445,446],{"class":158},"None",[73,448,17],{"class":120},[73,450,446],{"class":158},[73,452,17],{"class":120},[73,454,455],{"class":83},"\"missing\"\n",[73,457,459,461,463,466,469,472,476,478,481,483,485,487,490],{"class":75,"line":458},12,[73,460,383],{"class":120},[73,462,143],{"class":116},[73,464,465],{"class":120}," re.sub(",[73,467,468],{"class":116},"r",[73,470,471],{"class":83},"\"",[73,473,475],{"class":474},"s_b0D","\\.",[73,477,46],{"class":83},[73,479,480],{"class":158},"$",[73,482,471],{"class":83},[73,484,17],{"class":120},[73,486,409],{"class":83},[73,488,489],{"class":120},", text)                       ",[73,491,493],{"class":492},"s-wDw","# 7911123457.0 from a float cell\n",[73,495,497,500],{"class":75,"line":496},13,[73,498,499],{"class":116},"    try",[73,501,502],{"class":120},":\n",[73,504,506,509,511],{"class":75,"line":505},14,[73,507,508],{"class":120},"        number ",[73,510,143],{"class":116},[73,512,513],{"class":120}," phonenumbers.parse(text, country)\n",[73,515,517,520,523,525],{"class":75,"line":516},15,[73,518,519],{"class":116},"    except",[73,521,522],{"class":120}," NumberParseException ",[73,524,124],{"class":116},[73,526,527],{"class":120}," e:\n",[73,529,531,533,535,537,539,541,543,545,548,551,554,557,560],{"class":75,"line":530},16,[73,532,438],{"class":116},[73,534,441],{"class":158},[73,536,17],{"class":120},[73,538,446],{"class":158},[73,540,17],{"class":120},[73,542,446],{"class":158},[73,544,17],{"class":120},[73,546,547],{"class":116},"f",[73,549,550],{"class":83},"\"unparseable: ",[73,552,202],{"class":553},"sSjpA",[73,555,556],{"class":120},"e.error_type",[73,558,559],{"class":553},"}",[73,561,562],{"class":83},"\"\n",[73,564,566,568,571],{"class":75,"line":565},17,[73,567,397],{"class":116},[73,569,570],{"class":116}," not",[73,572,573],{"class":120}," phonenumbers.is_valid_number(number):\n",[73,575,577,579,581,583,585,587,589,591,594,597],{"class":75,"line":576},18,[73,578,438],{"class":116},[73,580,441],{"class":158},[73,582,17],{"class":120},[73,584,446],{"class":158},[73,586,17],{"class":120},[73,588,446],{"class":158},[73,590,17],{"class":120},[73,592,593],{"class":83},"\"invalid for \"",[73,595,596],{"class":116}," +",[73,598,599],{"class":120}," country\n",[73,601,603,606],{"class":75,"line":602},19,[73,604,605],{"class":116},"    return",[73,607,608],{"class":120}," (phonenumbers.format_number(number, PhoneNumberFormat.E164),\n",[73,610,612,615,618],{"class":75,"line":611},20,[73,613,614],{"class":120},"            phonenumbers.format_number(number, PhoneNumberFormat.",[73,616,617],{"class":158},"INTERNATIONAL",[73,619,620],{"class":120},"),\n",[73,622,624,627,630,633],{"class":75,"line":623},21,[73,625,626],{"class":158},"            TYPES",[73,628,629],{"class":120},".get(phonenumbers.number_type(number), ",[73,631,632],{"class":83},"\"other\"",[73,634,620],{"class":120},[73,636,638,641,643,646,648,651,653,656,658,661,664,667,670,673],{"class":75,"line":637},22,[73,639,640],{"class":83},"            \"ok\"",[73,642,596],{"class":116},[73,644,645],{"class":120}," (",[73,647,547],{"class":116},[73,649,650],{"class":83},"\" (ext ",[73,652,202],{"class":553},[73,654,655],{"class":120},"number.extension",[73,657,559],{"class":553},[73,659,660],{"class":83},")\"",[73,662,663],{"class":116}," if",[73,665,666],{"class":120}," number.extension ",[73,668,669],{"class":116},"else",[73,671,672],{"class":83}," \"\"",[73,674,675],{"class":120},"))\n",[73,677,679],{"class":75,"line":678},23,[73,680,134],{"emptyLinePlaceholder":133},[73,682,684,687,689,692,695,698,700,703,706],{"class":75,"line":683},24,[73,685,686],{"class":120},"out ",[73,688,143],{"class":116},[73,690,691],{"class":120}," customers.apply(",[73,693,694],{"class":116},"lambda",[73,696,697],{"class":120}," r: pd.Series(standardise(r[",[73,699,165],{"class":83},[73,701,702],{"class":120},"], r[",[73,704,705],{"class":83},"\"Country\"",[73,707,708],{"class":120},"]),\n",[73,710,712,715,717,720,723,725,728,730,733,735,738,741,744,746,749],{"class":75,"line":711},25,[73,713,714],{"class":196},"                                          index",[73,716,143],{"class":116},[73,718,719],{"class":120},"[",[73,721,722],{"class":83},"\"E164\"",[73,724,17],{"class":120},[73,726,727],{"class":83},"\"International\"",[73,729,17],{"class":120},[73,731,732],{"class":83},"\"Type\"",[73,734,17],{"class":120},[73,736,737],{"class":83},"\"Status\"",[73,739,740],{"class":120},"]), ",[73,742,743],{"class":196},"axis",[73,745,143],{"class":116},[73,747,748],{"class":158},"1",[73,750,152],{"class":120},[73,752,754,757,759,762,764,766,768],{"class":75,"line":753},26,[73,755,756],{"class":120},"result ",[73,758,143],{"class":116},[73,760,761],{"class":120}," pd.concat([customers, out], ",[73,763,743],{"class":196},[73,765,143],{"class":116},[73,767,748],{"class":158},[73,769,152],{"class":120},[73,771,773,775,778,781,783,785,787,789,791,793,795,797,800,803,805,807],{"class":75,"line":772},27,[73,774,159],{"class":158},[73,776,777],{"class":120},"(result[[",[73,779,780],{"class":83},"\"Customer\"",[73,782,17],{"class":120},[73,784,165],{"class":83},[73,786,17],{"class":120},[73,788,722],{"class":83},[73,790,17],{"class":120},[73,792,732],{"class":83},[73,794,17],{"class":120},[73,796,737],{"class":83},[73,798,799],{"class":120},"]].to_string(",[73,801,802],{"class":196},"index",[73,804,143],{"class":116},[73,806,221],{"class":158},[73,808,675],{"class":120},[10,810,811,814,815,819,820,823,824,827,828,17,830,833,834,837,838,841,842,845,846,849,850,852,853,856,857,859,860,863],{},[14,812,813],{},"phonenumbers.parse"," takes the text and a ",[816,817,818],"em",{},"default region"," — the country to assume when the number has no ",[14,821,822],{},"+"," country code — so each customer's own country is passed in. It understands the conventions humans use: ",[14,825,826],{},"+44 (0)20…"," loses the ",[14,829,38],{},[14,831,832],{},"0049"," is read as an international prefix, ",[14,835,836],{},"ext 42"," becomes an extension, and dots, spaces and brackets are ignored. ",[14,839,840],{},"is_valid_number"," then checks the number against that country's real numbering plan — length and allowed prefixes — so ",[14,843,844],{},"12345"," is rejected rather than turned into a plausible-looking ",[14,847,848],{},"+4412345",". Harbour Print's number, which Excel stored as the integer 7911123457, needs no special repair: without its trunk ",[14,851,46],{}," it is exactly the UK ",[816,854,855],{},"national significant number",", which ",[14,858,50],{}," accepts for the GB region, giving the same ",[14,861,862],{},"+447911123457"," the typed version would. That only works because the row's country is known — the same digits under a different default region would be a different, probably invalid, number. The line type distinguishes mobiles from landlines, which matters for anything that sends text messages.",[865,866,874,875,874,879,874,883,874,890,874,899,874,903,874,910,874,914,874,918,874,922,874,926,874,928,874,932,874,934,874,937,874,940,874,944,874,946,874,950,874,952,874,954,874,957,874,961,874,963,874,965,874,967,874,969,874,972,874,976,874,978,874,981,874,983,874,985,874,988,874,992,874,994,874,996,874,998,874,1001,874,1004,874,1008,874,1010,874,1012,874,1014,874,1016,874,1019,874,1023,874,1026,874,1030,874,1033,874,1035,874,1038,874,1040,874,1043,874,1045,874,1048,874,1050,874,1052,874,1054,874,1057,874,1059,874,1062,874,1064,874,1066,874,1068,874,1071,874,1073,874,1075,874,1077,874,1079,874,1081,874,1084,874,1086,874,1088,874,1090,874,1092,874,1094,874,1096,874,1098,874,1100,874,1102,874,1104,874,1106,874,1109,874,1114,874,1118,874,1124,874,1128,874,1135,874,1140,874,1148],"svg",{"viewBox":867,"role":868,"ariaLabelledBy":869,"xmlns":872,"style":873},"31 0 698 248","img",[870,871],"ph-norm-t","ph-norm-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:698px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[876,877,878],"title",{"id":870},"From typed numbers to E.164",[880,881,882],"desc",{"id":871},"Numbers typed in national, international and decorated forms become a single E.164 form with a plus sign, country code and no spaces, using each customer's country as the default region. A number Excel stored as an integer without its leading zero still parses for its country, and invalid or missing values are flagged instead of converted.",[884,885],"rect",{"x":886,"y":46,"width":887,"height":888,"fill":889},"31","698","248","#ffffff",[884,891],{"x":892,"y":893,"width":894,"height":895,"rx":46,"fill":896,"stroke":897,"style":898},"47.1","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[884,900],{"x":901,"y":893,"width":902,"height":895,"rx":46,"fill":896,"stroke":897,"style":898},"79.1","74.1",[904,905,909],"text",{"x":906,"y":907,"style":908},"116.1","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[884,911],{"x":912,"y":893,"width":913,"height":895,"rx":46,"fill":896,"stroke":897,"style":898},"153.1","157.9",[904,915,917],{"x":916,"y":907,"style":908},"232.1","B",[884,919],{"x":892,"y":920,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},"64.0","28.0",[904,923,748],{"x":924,"y":925,"style":908},"63.1","83.0",[884,927],{"x":901,"y":920,"width":902,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,929,931],{"x":906,"y":925,"style":930},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Country",[884,933],{"x":912,"y":920,"width":913,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,935,936],{"x":916,"y":925,"style":930},"Phone as typed",[884,938],{"x":892,"y":939,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},"92.0",[904,941,943],{"x":924,"y":942,"style":908},"111.0","2",[884,945],{"x":901,"y":939,"width":902,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,947,949],{"x":906,"y":942,"style":948},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","GB",[884,951],{"x":912,"y":939,"width":913,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,953,16],{"x":916,"y":942,"style":948},[884,955],{"x":892,"y":956,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},"120.0",[904,958,960],{"x":924,"y":959,"style":908},"139.0","3",[884,962],{"x":901,"y":956,"width":902,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,964,949],{"x":906,"y":959,"style":948},[884,966],{"x":912,"y":956,"width":913,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,968,20],{"x":916,"y":959,"style":948},[884,970],{"x":892,"y":971,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},"148.0",[904,973,975],{"x":924,"y":974,"style":908},"167.0","4",[884,977],{"x":901,"y":971,"width":902,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,979,980],{"x":906,"y":974,"style":948},"DE",[884,982],{"x":912,"y":971,"width":913,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,984,99],{"x":916,"y":974,"style":948},[884,986],{"x":892,"y":987,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},"176.0",[904,989,991],{"x":924,"y":990,"style":908},"195.0","5",[884,993],{"x":901,"y":987,"width":902,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,995,949],{"x":906,"y":990,"style":948},[884,997],{"x":912,"y":987,"width":913,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,999,1000],{"x":916,"y":990,"style":948},"7911123457 (number)",[884,1002],{"x":892,"y":1003,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},"204.0",[904,1005,1007],{"x":924,"y":1006,"style":908},"223.0","6",[884,1009],{"x":901,"y":1003,"width":902,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1011,949],{"x":906,"y":1006,"style":948},[884,1013],{"x":912,"y":1003,"width":913,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1015,844],{"x":916,"y":1006,"style":948},[884,1017],{"x":1018,"y":893,"width":894,"height":895,"rx":46,"fill":896,"stroke":897,"style":898},"485.1",[884,1020],{"x":1021,"y":893,"width":1022,"height":895,"rx":46,"fill":896,"stroke":897,"style":898},"517.1","130.9",[904,1024,909],{"x":1025,"y":907,"style":908},"582.5",[884,1027],{"x":1028,"y":893,"width":1029,"height":895,"rx":46,"fill":896,"stroke":897,"style":898},"648.0","64.9",[904,1031,917],{"x":1032,"y":907,"style":908},"680.5",[884,1034],{"x":1018,"y":920,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},[904,1036,748],{"x":1037,"y":925,"style":908},"501.1",[884,1039],{"x":1021,"y":920,"width":1022,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1041,1042],{"x":1025,"y":925,"style":930},"E.164",[884,1044],{"x":1028,"y":920,"width":1029,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1046,1047],{"x":1032,"y":925,"style":930},"Status",[884,1049],{"x":1018,"y":939,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},[904,1051,943],{"x":1037,"y":942,"style":908},[884,1053],{"x":1021,"y":939,"width":1022,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1055,1056],{"x":1025,"y":942,"style":948},"+447911123456",[884,1058],{"x":1028,"y":939,"width":1029,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1060,1061],{"x":1032,"y":942,"style":948},"ok",[884,1063],{"x":1018,"y":956,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},[904,1065,960],{"x":1037,"y":959,"style":908},[884,1067],{"x":1021,"y":956,"width":1022,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1069,1070],{"x":1025,"y":959,"style":948},"+442079460958",[884,1072],{"x":1028,"y":956,"width":1029,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1074,1061],{"x":1032,"y":959,"style":948},[884,1076],{"x":1018,"y":971,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},[904,1078,975],{"x":1037,"y":974,"style":908},[884,1080],{"x":1021,"y":971,"width":1022,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1082,1083],{"x":1025,"y":974,"style":948},"+4930901820",[884,1085],{"x":1028,"y":971,"width":1029,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1087,1061],{"x":1032,"y":974,"style":948},[884,1089],{"x":1018,"y":987,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},[904,1091,991],{"x":1037,"y":990,"style":908},[884,1093],{"x":1021,"y":987,"width":1022,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1095,862],{"x":1025,"y":990,"style":948},[884,1097],{"x":1028,"y":987,"width":1029,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1099,1061],{"x":1032,"y":990,"style":948},[884,1101],{"x":1018,"y":1003,"width":894,"height":921,"rx":46,"fill":896,"stroke":897,"style":898},[904,1103,1007],{"x":1037,"y":1006,"style":908},[884,1105],{"x":1021,"y":1003,"width":1022,"height":921,"rx":46,"fill":889,"stroke":897,"style":898},[904,1107,1108],{"x":1025,"y":1006,"style":948},"—",[884,1110],{"x":1028,"y":1003,"width":1029,"height":921,"rx":46,"fill":1111,"stroke":1112,"style":1113},"#fdefd8","var(--gold,#b4740a)","stroke-width:1.5px",[904,1115,1117],{"x":1032,"y":1006,"style":1116},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","invalid",[904,1119,1123],{"x":1120,"y":1121,"style":1122},"179.1","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[904,1125,1127],{"x":1126,"y":1121,"style":1122},"599.0","after",[75,1129],{"x1":1130,"y1":1131,"x2":1132,"y2":1131,"stroke":1133,"style":1134},"321.0","136.0","471.1","var(--brand,#5b5cf0)","stroke-width:2px",[1136,1137],"polygon",{"points":1138,"fill":1139},"475.1,136.0 466.1,141.0 466.1,131.0","#5b5cf0",[884,1141],{"x":1142,"y":1143,"width":1144,"height":1145,"rx":1146,"fill":889,"stroke":897,"style":1147},"344.4","103.0","107.2","22.0","11","stroke-width:1.2px",[904,1149,50],{"x":1150,"y":1151,"style":1152},"398.1","118.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",[59,1154,1156],{"id":1155},"step-3-review-list-and-write-back-as-text","Step 3 — Review list and write back as text",[10,1158,1159],{},"Not every number can be fixed automatically. Separate the ones that need a person, and write the results so Excel cannot damage them again:",[64,1161,1163],{"className":107,"code":1162,"language":109,"meta":69,"style":69},"review = result[~result[\"Status\"].str.startswith(\"ok\")]\nprint(review[[\"Customer\", \"Phone\", \"Status\"]].to_string(index=False))\n\nfrom openpyxl import load_workbook\nwith pd.ExcelWriter(\"customers_clean.xlsx\", engine=\"openpyxl\") as xw:\n    result.to_excel(xw, sheet_name=\"Customers\", index=False)\n    review.to_excel(xw, sheet_name=\"Needs review\", index=False)\nwb = load_workbook(\"customers_clean.xlsx\")\nws = wb[\"Customers\"]\nfor col in (\"C\", \"D\", \"E\"):\n    for cell in ws[col][1:]:\n        cell.number_format = \"@\"\nwb.save(\"customers_clean.xlsx\")\ncheck = pd.read_excel(\"customers_clean.xlsx\", dtype=str)[\"E164\"].dropna().tolist()\nprint(\"E.164 values all start with '+':\", all(v.startswith(\"+\") for v in check))\n",[14,1164,1165,1192,1219,1223,1235,1264,1287,1309,1323,1338,1366,1384,1394,1403,1430],{"__ignoreMap":69},[73,1166,1167,1170,1172,1175,1178,1181,1183,1186,1189],{"class":75,"line":76},[73,1168,1169],{"class":120},"review ",[73,1171,143],{"class":116},[73,1173,1174],{"class":120}," result[",[73,1176,1177],{"class":116},"~",[73,1179,1180],{"class":120},"result[",[73,1182,737],{"class":83},[73,1184,1185],{"class":120},"].str.startswith(",[73,1187,1188],{"class":83},"\"ok\"",[73,1190,1191],{"class":120},")]\n",[73,1193,1194,1196,1199,1201,1203,1205,1207,1209,1211,1213,1215,1217],{"class":75,"line":130},[73,1195,159],{"class":158},[73,1197,1198],{"class":120},"(review[[",[73,1200,780],{"class":83},[73,1202,17],{"class":120},[73,1204,165],{"class":83},[73,1206,17],{"class":120},[73,1208,737],{"class":83},[73,1210,799],{"class":120},[73,1212,802],{"class":196},[73,1214,143],{"class":116},[73,1216,221],{"class":158},[73,1218,675],{"class":120},[73,1220,1221],{"class":75,"line":137},[73,1222,134],{"emptyLinePlaceholder":133},[73,1224,1225,1227,1230,1232],{"class":75,"line":155},[73,1226,289],{"class":116},[73,1228,1229],{"class":120}," openpyxl ",[73,1231,117],{"class":116},[73,1233,1234],{"class":120}," load_workbook\n",[73,1236,1237,1240,1243,1246,1248,1251,1253,1256,1259,1261],{"class":75,"line":177},[73,1238,1239],{"class":116},"with",[73,1241,1242],{"class":120}," pd.ExcelWriter(",[73,1244,1245],{"class":83},"\"customers_clean.xlsx\"",[73,1247,17],{"class":120},[73,1249,1250],{"class":196},"engine",[73,1252,143],{"class":116},[73,1254,1255],{"class":83},"\"openpyxl\"",[73,1257,1258],{"class":120},") ",[73,1260,124],{"class":116},[73,1262,1263],{"class":120}," xw:\n",[73,1265,1266,1269,1272,1274,1277,1279,1281,1283,1285],{"class":75,"line":182},[73,1267,1268],{"class":120},"    result.to_excel(xw, ",[73,1270,1271],{"class":196},"sheet_name",[73,1273,143],{"class":116},[73,1275,1276],{"class":83},"\"Customers\"",[73,1278,17],{"class":120},[73,1280,802],{"class":196},[73,1282,143],{"class":116},[73,1284,221],{"class":158},[73,1286,152],{"class":120},[73,1288,1289,1292,1294,1296,1299,1301,1303,1305,1307],{"class":75,"line":226},[73,1290,1291],{"class":120},"    review.to_excel(xw, ",[73,1293,1271],{"class":196},[73,1295,143],{"class":116},[73,1297,1298],{"class":83},"\"Needs review\"",[73,1300,17],{"class":120},[73,1302,802],{"class":196},[73,1304,143],{"class":116},[73,1306,221],{"class":158},[73,1308,152],{"class":120},[73,1310,1311,1314,1316,1319,1321],{"class":75,"line":367},[73,1312,1313],{"class":120},"wb ",[73,1315,143],{"class":116},[73,1317,1318],{"class":120}," load_workbook(",[73,1320,1245],{"class":83},[73,1322,152],{"class":120},[73,1324,1325,1328,1330,1333,1335],{"class":75,"line":380},[73,1326,1327],{"class":120},"ws ",[73,1329,143],{"class":116},[73,1331,1332],{"class":120}," wb[",[73,1334,1276],{"class":83},[73,1336,1337],{"class":120},"]\n",[73,1339,1340,1343,1346,1348,1350,1353,1355,1358,1360,1363],{"class":75,"line":394},[73,1341,1342],{"class":116},"for",[73,1344,1345],{"class":120}," col ",[73,1347,403],{"class":116},[73,1349,645],{"class":120},[73,1351,1352],{"class":83},"\"C\"",[73,1354,17],{"class":120},[73,1356,1357],{"class":83},"\"D\"",[73,1359,17],{"class":120},[73,1361,1362],{"class":83},"\"E\"",[73,1364,1365],{"class":120},"):\n",[73,1367,1368,1371,1374,1376,1379,1381],{"class":75,"line":435},[73,1369,1370],{"class":116},"    for",[73,1372,1373],{"class":120}," cell ",[73,1375,403],{"class":116},[73,1377,1378],{"class":120}," ws[col][",[73,1380,748],{"class":158},[73,1382,1383],{"class":120},":]:\n",[73,1385,1386,1389,1391],{"class":75,"line":458},[73,1387,1388],{"class":120},"        cell.number_format ",[73,1390,143],{"class":116},[73,1392,1393],{"class":83}," \"@\"\n",[73,1395,1396,1399,1401],{"class":75,"line":496},[73,1397,1398],{"class":120},"wb.save(",[73,1400,1245],{"class":83},[73,1402,152],{"class":120},[73,1404,1405,1408,1410,1412,1414,1416,1418,1420,1422,1425,1427],{"class":75,"line":505},[73,1406,1407],{"class":120},"check ",[73,1409,143],{"class":116},[73,1411,146],{"class":120},[73,1413,1245],{"class":83},[73,1415,17],{"class":120},[73,1417,197],{"class":196},[73,1419,143],{"class":116},[73,1421,210],{"class":158},[73,1423,1424],{"class":120},")[",[73,1426,722],{"class":83},[73,1428,1429],{"class":120},"].dropna().tolist()\n",[73,1431,1432,1434,1437,1440,1442,1445,1448,1451,1453,1455,1458,1460],{"class":75,"line":516},[73,1433,159],{"class":158},[73,1435,1436],{"class":120},"(",[73,1438,1439],{"class":83},"\"E.164 values all start with '+':\"",[73,1441,17],{"class":120},[73,1443,1444],{"class":158},"all",[73,1446,1447],{"class":120},"(v.startswith(",[73,1449,1450],{"class":83},"\"+\"",[73,1452,1258],{"class":120},[73,1454,1342],{"class":116},[73,1456,1457],{"class":120}," v ",[73,1459,403],{"class":116},[73,1461,1462],{"class":120}," check))\n",[10,1464,1465,1466,1468,1469,1472,1473,1475,1476,1478,1479,1482],{},"Two customers go to review: Atlas Supply's ",[14,1467,844],{}," is not a valid UK number and Corex has no number. Every other entry was standardised — including Harbour Print's number from the integer cell, and Delta Office's, with its extension kept separately. Whether a number is a mobile matters as much as whether it is valid when the column feeds a text-message campaign: Zenith's and Delta's US numbers come back as \"landline or mobile\", because North American numbering does not distinguish the two, so a campaign there needs a separate check with the carrier. The cleaned columns are written as text and given the text format ",[14,1470,1471],{},"@",", so a number like ",[14,1474,1056],{}," is never reinterpreted as a numeric value by someone editing the sheet, and the round trip confirms every E.164 value kept its ",[14,1477,822],{},". Store E.164 in systems — it is unambiguous and what SMS and telephony APIs expect — and show the international format to people, who read ",[14,1480,1481],{},"+44 7911 123456"," more easily.",[865,1484,874,1490,874,1493,874,1496,874,1500,874,1508,874,1519,874,1524,874,1528,874,1532,874,1535,874,1538,874,1545,874,1549,874,1555,874,1562,874,1566,874,1571,874,1574,874,1577,874,1581,874,1584,874,1588,874,1595,874,1599,874,1602,874,1605,874,1608,874,1612,874,1615,874,1618,874,1625,874,1629,874,1632,874,1634,874,1637,874,1641,874,1644,874,1647,874,1653,874,1655],{"viewBox":1485,"role":868,"ariaLabelledBy":1486,"xmlns":872,"style":1489},"4 0 752 272",[1487,1488],"ph-route-t","ph-route-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[876,1491,1492],{"id":1487},"What happens to each phone value",[880,1494,1495],{"id":1488},"Missing placeholders are flagged as missing. Numbers that parse and validate for the customer's country are standardised, including digits that lost their leading zero in Excel. Numbers that parse but fail validation, and text that cannot be parsed, go to a review sheet rather than being guessed.",[884,1497],{"x":975,"y":46,"width":1498,"height":1499,"fill":889},"752","272",[884,1501],{"x":1502,"y":1503,"width":1504,"height":1505,"rx":1506,"fill":1507,"stroke":1133,"style":1134},"240.0","16.0","280.0","75.5","12","#f0f4ff",[1509,1510,1512],"g",{"transform":1511},"translate(368.0,29.0) scale(1.000)",[1513,1514],"path",{"d":1515,"fill":1516,"stroke":1517,"style":1518},"M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z M4 21c0-4 3.6-7 8-7s8 3 8 7","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[904,1520,1523],{"x":1521,"y":1505,"style":1522},"380.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Phone value",[75,1525],{"x1":1521,"y1":1526,"x2":1521,"y2":1527,"stroke":1133,"style":1134},"91.5","111.5",[75,1529],{"x1":1530,"y1":1527,"x2":1531,"y2":1527,"stroke":1133,"style":1134},"104.0","656.0",[75,1533],{"x1":1530,"y1":1527,"x2":1530,"y2":1534,"stroke":1133,"style":1134},"157.5",[1136,1536],{"points":1537,"fill":1139},"104.0,161.5 99.0,152.5 109.0,152.5",[884,1539],{"x":1540,"y":1541,"width":1542,"height":1543,"rx":1544,"fill":889,"stroke":897,"style":1147},"60.2","121.5","87.7","21.0","10",[904,1546,1548],{"x":1530,"y":1131,"style":1547},"font-size:11.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","n\u002Fa or blank",[884,1550],{"x":1551,"y":1552,"width":1553,"height":1554,"rx":1506,"fill":896,"stroke":897,"style":1134},"20.0","163.5","168.0","93.0",[1509,1556,1558],{"transform":1557},"translate(92.0,176.5) scale(1.000)",[1513,1559],{"d":1560,"fill":1516,"stroke":1561,"style":1518},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","var(--text,#172033)",[904,1563,1565],{"x":1530,"y":1006,"style":1564},"font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","missing",[904,1567,1570],{"x":1530,"y":1568,"style":1569},"242.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","ask for a number",[75,1572],{"x1":1573,"y1":1527,"x2":1573,"y2":1534,"stroke":1133,"style":1134},"288.0",[1136,1575],{"points":1576,"fill":1139},"288.0,161.5 283.0,152.5 293.0,152.5",[884,1578],{"x":1579,"y":1541,"width":1580,"height":1543,"rx":1544,"fill":889,"stroke":897,"style":1147},"230.3","115.4",[904,1582,1583],{"x":1573,"y":1131,"style":1547},"valid for country",[884,1585],{"x":1003,"y":1552,"width":1553,"height":1554,"rx":1506,"fill":1586,"stroke":1587,"style":1134},"#d9f4f1","var(--teal,#0f9488)",[1509,1589,1591],{"transform":1590},"translate(276.0,176.5) scale(1.000)",[1513,1592],{"d":1593,"fill":1516,"stroke":1594,"style":1518},"M5 12.5l4.5 4.5L19 7","var(--teal-ink,#0b6157)",[904,1596,1598],{"x":1573,"y":1006,"style":1597},"font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","standardised",[904,1600,1601],{"x":1573,"y":1568,"style":1569},"E.164 + international",[75,1603],{"x1":1604,"y1":1527,"x2":1604,"y2":1534,"stroke":1133,"style":1134},"472.0",[1136,1606],{"points":1607,"fill":1139},"472.0,161.5 467.0,152.5 477.0,152.5",[884,1609],{"x":1610,"y":1541,"width":1611,"height":1543,"rx":1544,"fill":889,"stroke":897,"style":1147},"410.6","122.7",[904,1613,1614],{"x":1604,"y":1131,"style":1547},"parses but invalid",[884,1616],{"x":1617,"y":1552,"width":1553,"height":1554,"rx":1506,"fill":1111,"stroke":1112,"style":1134},"388.0",[1509,1619,1621],{"transform":1620},"translate(460.0,176.5) scale(1.000)",[1513,1622],{"d":1623,"fill":1516,"stroke":1624,"style":1518},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--gold-ink,#7a4e06)",[904,1626,1628],{"x":1604,"y":1006,"style":1627},"font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","review sheet",[904,1630,1631],{"x":1604,"y":1568,"style":1569},"e.g. 12345",[75,1633],{"x1":1531,"y1":1527,"x2":1531,"y2":1534,"stroke":1133,"style":1134},[1136,1635],{"points":1636,"fill":1139},"656.0,161.5 651.0,152.5 661.0,152.5",[884,1638],{"x":1639,"y":1541,"width":1640,"height":1543,"rx":1544,"fill":889,"stroke":897,"style":1147},"597.2","117.5",[904,1642,1643],{"x":1531,"y":1131,"style":1547},"unparseable text",[884,1645],{"x":1646,"y":1552,"width":1553,"height":1554,"rx":1506,"fill":1507,"stroke":1133,"style":1134},"572.0",[1509,1648,1650],{"transform":1649},"translate(644.0,176.5) scale(1.000)",[1513,1651],{"d":1652,"fill":1516,"stroke":1517,"style":1518},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4",[904,1654,1628],{"x":1531,"y":1006,"style":1522},[904,1656,1657],{"x":1531,"y":1568,"style":1569},"never guessed",[59,1659,1661],{"id":1660},"keeping-it-clean-afterwards","Keeping it clean afterwards",[10,1663,1664,1665,1667],{},"Cleaning a column once is half the job; the same mixture comes back the next time people type into the sheet. Three habits keep it clean. Run the standardisation as part of the import — every time the file is loaded — rather than as a one-off fix, so the system downstream only ever sees E.164. Keep the country column mandatory, because the parsing depends on it; a missing country is worth flagging as loudly as a missing number. And if the workbook is a form people fill in, format the phone column as text before it is distributed, so Excel never converts a typed number into an integer in the first place. The ",[14,1666,50],{}," metadata is updated as countries change their numbering plans, so upgrading the package from time to time keeps validation accurate.",[59,1669,1671],{"id":1670},"step-4-use-the-standard-form-to-find-duplicates","Step 4 — Use the standard form to find duplicates",[10,1673,1674],{},"A side effect of standardising is that the same number written two ways becomes the same string, which makes duplicate contacts visible:",[64,1676,1678],{"className":107,"code":1677,"language":109,"meta":69,"style":69},"dupes = result.dropna(subset=[\"E164\"]).groupby(\"E164\")[\"Customer\"].agg(list)\nprint(dupes[dupes.map(len) > 1].to_string())\n",[14,1679,1680,1716],{"__ignoreMap":69},[73,1681,1682,1685,1687,1690,1693,1695,1697,1699,1702,1704,1706,1708,1711,1714],{"class":75,"line":76},[73,1683,1684],{"class":120},"dupes ",[73,1686,143],{"class":116},[73,1688,1689],{"class":120}," result.dropna(",[73,1691,1692],{"class":196},"subset",[73,1694,143],{"class":116},[73,1696,719],{"class":120},[73,1698,722],{"class":83},[73,1700,1701],{"class":120},"]).groupby(",[73,1703,722],{"class":83},[73,1705,1424],{"class":120},[73,1707,780],{"class":83},[73,1709,1710],{"class":120},"].agg(",[73,1712,1713],{"class":158},"list",[73,1715,152],{"class":120},[73,1717,1718,1720,1723,1726,1728,1731,1734],{"class":75,"line":130},[73,1719,159],{"class":158},[73,1721,1722],{"class":120},"(dupes[dupes.map(",[73,1724,1725],{"class":158},"len",[73,1727,1258],{"class":120},[73,1729,1730],{"class":116},">",[73,1732,1733],{"class":158}," 1",[73,1735,1736],{"class":120},"].to_string())\n",[10,1738,1739,1740,1742,1743,1745],{},"Nordwind GmbH's ",[14,1741,26],{}," and Granite Tools' ",[14,1744,99],{}," are the same Berlin number, which no comparison of the raw text would have revealed. Whether that means a duplicate customer, a shared switchboard or a data-entry mistake is a question for a person, but the check costs one line once the numbers share a format. The same idea applies to matching the spreadsheet against a CRM or telephony system: compare on E.164, never on the number as typed.",[865,1747,874,1752,874,1755,874,1758,874,1761,874,1765,874,1771,874,1775,874,1779,874,1784,874,1787,874,1790,874,1795,874,1800,874,1804,874,1807,874,1811,874,1814,874,1817,874,1822,874,1825,874,1829,874,1832,874,1836,874,1839,874,1842,874,1848,874,1852,874,1855,874,1858,874,1862,874,1865,874,1868,874,1874,874,1878,874,1881,874,1884],{"viewBox":1748,"role":868,"ariaLabelledBy":1749,"xmlns":872,"style":1489},"4 0 752 160",[1750,1751],"ph-pipeline-t","ph-pipeline-d",[876,1753,1754],{"id":1750},"The phone cleaning pipeline",[880,1756,1757],{"id":1751},"Read the column as text, parse each number with the customer's country, validate it against the country's numbering plan, write E.164 and international formats with line types, send failures to a review sheet, and use E.164 to find duplicates.",[884,1759],{"x":975,"y":46,"width":1498,"height":1760,"fill":889},"160",[884,1762],{"x":1551,"y":1503,"width":1763,"height":1764,"rx":1506,"fill":896,"stroke":897,"style":1134},"115.2","128.0",[1509,1766,1768],{"transform":1767},"translate(65.6,46.5) scale(1.000)",[1513,1769],{"d":1770,"fill":1516,"stroke":1561,"style":1518},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14",[904,1772,1774],{"x":1773,"y":1554,"style":1564},"77.6","Read as text",[904,1776,1778],{"x":1773,"y":1777,"style":1569},"112.5","dtype=str",[75,1780],{"x1":1781,"y1":1782,"x2":1783,"y2":1782,"stroke":1133,"style":1134},"140.2","80.0","162.2",[1136,1785],{"points":1786,"fill":1139},"166.2,80.0 157.2,85.0 157.2,75.0",[884,1788],{"x":1789,"y":1503,"width":1763,"height":1764,"rx":1506,"fill":1507,"stroke":1133,"style":1134},"171.2",[1509,1791,1793],{"transform":1792},"translate(216.8,37.8) scale(1.000)",[1513,1794],{"d":1560,"fill":1516,"stroke":1517,"style":1518},[904,1796,1799],{"x":1797,"y":1798,"style":1522},"228.8","84.2","Parse per",[904,1801,1803],{"x":1797,"y":1802,"style":1522},"101.8","country",[904,1805,818],{"x":1797,"y":1806,"style":1569},"121.2",[75,1808],{"x1":1809,"y1":1782,"x2":1810,"y2":1782,"stroke":1133,"style":1134},"291.4","313.4",[1136,1812],{"points":1813,"fill":1139},"317.4,80.0 308.4,85.0 308.4,75.0",[884,1815],{"x":1816,"y":1503,"width":1763,"height":1764,"rx":1506,"fill":1586,"stroke":1587,"style":1134},"322.4",[1509,1818,1820],{"transform":1819},"translate(368.0,37.8) scale(1.000)",[1513,1821],{"d":1593,"fill":1516,"stroke":1594,"style":1518},[904,1823,1824],{"x":1521,"y":1798,"style":1597},"Validate",[904,1826,1828],{"x":1521,"y":1827,"style":1569},"103.8","per numbering",[904,1830,1831],{"x":1521,"y":1806,"style":1569},"plan",[75,1833],{"x1":1834,"y1":1782,"x2":1835,"y2":1782,"stroke":1133,"style":1134},"442.6","464.6",[1136,1837],{"points":1838,"fill":1139},"468.6,80.0 459.6,85.0 459.6,75.0",[884,1840],{"x":1841,"y":1503,"width":1763,"height":1764,"rx":1506,"fill":896,"stroke":897,"style":1134},"473.6",[1509,1843,1845],{"transform":1844},"translate(519.2,37.8) scale(1.000)",[1513,1846],{"d":1847,"fill":1516,"stroke":1561,"style":1518},"M6 3h9l4 4v14H6z M15 3v4h4",[904,1849,1851],{"x":1850,"y":1798,"style":1564},"531.2","Write as text",[904,1853,1854],{"x":1850,"y":1827,"style":1569},"E.164 +",[904,1856,1857],{"x":1850,"y":1806,"style":1569},"international",[75,1859],{"x1":1860,"y1":1782,"x2":1861,"y2":1782,"stroke":1133,"style":1134},"593.8","615.8",[1136,1863],{"points":1864,"fill":1139},"619.8,80.0 610.8,85.0 610.8,75.0",[884,1866],{"x":1867,"y":1503,"width":1763,"height":1764,"rx":1506,"fill":1111,"stroke":1112,"style":1134},"624.8",[1509,1869,1871],{"transform":1870},"translate(670.4,29.0) scale(1.000)",[1513,1872],{"d":1873,"fill":1516,"stroke":1624,"style":1518},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7",[904,1875,1877],{"x":1876,"y":1505,"style":1627},"682.4","Review and",[904,1879,1880],{"x":1876,"y":1554,"style":1627},"dedupe",[904,1882,1883],{"x":1876,"y":1777,"style":1569},"failures and",[904,1885,1887],{"x":1876,"y":1886,"style":1569},"130.0","duplicates",[59,1889,1891],{"id":1890},"common-pitfalls","Common pitfalls",[1893,1894,1895,1908,1914,1920,1929],"ul",{},[1896,1897,1898,1902,1903,1905,1906,57],"li",{},[1899,1900,1901],"strong",{},"Reading phone columns as numbers."," Leading zeros and ",[14,1904,822],{}," signs are lost; use ",[14,1907,1778],{},[1896,1909,1910,1913],{},[1899,1911,1912],{},"One default country for everyone."," Pass each row's own country.",[1896,1915,1916,1919],{},[1899,1917,1918],{},"Regex-only cleaning."," It cannot know each country's trunk prefixes and lengths.",[1896,1921,1922,1925,1926,1928],{},[1899,1923,1924],{},"Accepting anything that parses."," Check ",[14,1927,840],{}," as well.",[1896,1930,1931,1934],{},[1899,1932,1933],{},"Writing E.164 back as numbers."," Format the cells as text so Excel cannot change them.",[59,1936,1938],{"id":1937},"conclusion","Conclusion",[10,1940,1941,1942,1944,1945,1947],{},"Read phone columns as text, parse each number with ",[14,1943,50],{}," using the row's own country as the default region, and keep only numbers that pass ",[14,1946,840],{},"; digit-only values that lost their trunk zero in Excel still parse correctly when the country is right. Store E.164 for systems and the international format for people, record line type and extensions, send everything that fails to a review sheet, and write the results back as text.",[59,1949,1951],{"id":1950},"frequently-asked-questions","Frequently asked questions",[10,1953,1954,1957],{},[1899,1955,1956],{},"How do I format phone numbers consistently in Python?","\nParse them with the phonenumbers library, passing a default country, and format with PhoneNumberFormat.E164 or INTERNATIONAL.",[10,1959,1960,1963],{},[1899,1961,1962],{},"Why are leading zeros missing from phone numbers read from Excel?","\nThe cells were stored or read as numbers; read the column with dtype=str, and repair numbers Excel already converted by restoring the trunk zero.",[10,1965,1966,1969],{},[1899,1967,1968],{},"How do I check whether a phone number is valid?","\nUse phonenumbers.is_valid_number on the parsed number; it checks the number against the country's numbering plan.",[10,1971,1972,1975],{},[1899,1973,1974],{},"How can I tell mobile numbers from landlines?","\nphonenumbers.number_type returns MOBILE, FIXED_LINE or another type for a parsed number.",[59,1977,1979],{"id":1978},"related","Related",[1893,1981,1982,1988,1995,2002],{},[1896,1983,1984,1985,1987],{},"Up one level: ",[53,1986,56],{"href":55}," — tidying Excel data.",[1896,1989,1990,1994],{},[53,1991,1993],{"href":1992},"\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 with Python"," — checking formats without converting.",[1896,1996,1997,2001],{},[53,1998,2000],{"href":1999},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-country-names-and-codes-with-python\u002F","Standardise Country Names and Codes with Python"," — cleaning the country column first.",[1896,2003,2004,2008],{},[53,2005,2007],{"href":2006},"\u002Fformatting-and-charting-excel-reports-with-python\u002Fapplying-number-and-date-formats-in-excel\u002Fformat-zip-codes-and-ids-with-leading-zeros\u002F","Format ZIP Codes and IDs with Leading Zeros"," — keeping codes intact in Excel.",[2010,2011,2012],"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 .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":69,"searchDepth":130,"depth":130,"links":2014},[2015,2016,2017,2018,2019,2020,2021,2022,2023,2024],{"id":61,"depth":130,"text":62},{"id":103,"depth":130,"text":104},{"id":265,"depth":130,"text":266},{"id":1155,"depth":130,"text":1156},{"id":1660,"depth":130,"text":1661},{"id":1670,"depth":130,"text":1671},{"id":1890,"depth":130,"text":1891},{"id":1937,"depth":130,"text":1938},{"id":1950,"depth":130,"text":1951},{"id":1978,"depth":130,"text":1979},"2026-10-02","Turn a column of phone numbers typed every possible way — spaces, brackets, (0), 00 prefixes, missing country codes, numbers mangled into floats by Excel — into one consistent format with pandas and the phonenumbers library: E.164 for systems, international or national format for people, validity and line-type checks, and a review list of numbers that cannot be trusted.","md",[2029,2031,2033,2035],{"q":1956,"a":2030},"Parse them with the phonenumbers library, passing a default country, and format with PhoneNumberFormat.E164 or INTERNATIONAL.",{"q":1962,"a":2032},"The cells were stored or read as numbers; read the column with dtype=str, and repair numbers Excel already converted by restoring the trunk zero.",{"q":1968,"a":2034},"Use phonenumbers.is_valid_number on the parsed number; it checks the number against the country's numbering plan.",{"q":1974,"a":2036},"phonenumbers.number_type returns MOBILE, FIXED_LINE or another type for a parsed number.","standardise phone numbers in excel data python",null,{"breadcrumb":2040},[2041,2044,2047],{"name":2042,"item":2043},"Home","\u002F",{"name":2045,"item":2046},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2048,"item":55},"Cleaning Excel Data With Pandas: A Production Workflow","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python",{"title":5,"description":2051},"Clean phone numbers from Excel with phonenumbers and pandas: read as text, default regions per row, E.164 and international formats, validity and mobile checks, repairing numbers Excel turned into floats, and writing them back as text.","standardise-phone-numbers-in-excel-data-with-python","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-phone-numbers-in-excel-data-with-python\u002Findex",[2055,2056],"data-cleaning","pandas","how-to","iBMSEfjssqlj8NIxpIllMiGRJgCBX93gTpF50id9SeY",[2060,2064],{"title":2061,"path":2062,"stem":2063,"children":-1},"Standardise Excel Column Names with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas\u002Findex",{"title":2065,"path":2066,"stem":2067,"children":-1},"Strip Whitespace and Normalise Text Columns with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstrip-whitespace-and-normalise-text-columns-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstrip-whitespace-and-normalise-text-columns-with-pandas\u002Findex",[2069,2071],{"path":2070,"title":2048},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas",{"path":2072,"title":2045},"\u002Fadvanced-data-transformation-and-cleaning",[],[2075,2078,2081,2084,2087,2090],{"path":2076,"title":2077,"kind":2057},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Ffind-duplicate-rows-in-excel-with-python","Find Duplicate Rows in Excel with Python",{"path":2079,"title":2080,"kind":2057},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-duplicate-values-in-excel-with-openpyxl","Highlight Duplicate Values in Excel with openpyxl",{"path":2082,"title":2083,"kind":2057},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fclean-mixed-currency-amounts-into-amount-and-currency-columns","Clean Mixed-Currency Amounts into Amount and Currency Columns",{"path":2085,"title":2086,"kind":2057},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fcoalesce-several-excel-columns-into-one-with-pandas","Coalesce Several Excel Columns into One with pandas",{"path":2088,"title":2089,"kind":2057},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-currency-strings-to-numbers-with-pandas","Convert Currency Strings Like \"$1,234.56\" and \"(500)\" to Numbers with pandas",{"path":2091,"title":2092,"kind":2057},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fconvert-excel-text-columns-to-numbers-with-pandas","Convert Excel Text Columns to Numbers with pandas",1790972086059]