[{"data":1,"prerenderedAt":2406},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas":2372,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas":2381,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas":2386,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas":2387},{"id":4,"title":5,"body":6,"dateModified":2337,"datePublished":2337,"description":2338,"extension":2339,"faq":2340,"keyword":2349,"kind":2350,"meta":2351,"navigation":130,"path":2361,"seo":2362,"slug":2365,"stem":2366,"topics":2367,"type":2370,"__hash__":2371},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas\u002Findex.md","Normalise Postcodes and ZIP Codes with pandas",{"type":7,"value":8,"toc":2325},"minimark",[9,56,61,89,95,99,102,201,226,230,237,430,443,447,450,1333,1362,1617,1621,1624,1735,1749,1760,1847,1851,1854,2085,2099,2211,2215,2253,2257,2260,2264,2270,2276,2282,2288,2292,2321],[10,11,12,13,17,18,21,22,25,26,29,30,33,34,37,38,41,42,45,46,49,50,55],"p",{},"Postcodes are the field that address lists get wrong most often. Excel helpfully turns the Boston ZIP code ",[14,15,16],"code",{},"02134"," into the number ",[14,19,20],{},"2134",", and the German postcode ",[14,23,24],{},"01067"," into ",[14,27,28],{},"1067",". UK postcodes arrive as ",[14,31,32],{},"sw1a1aa",", ",[14,35,36],{},"SW1A  1AA"," and ",[14,39,40],{},"SW1A1AA",", all meaning the same place. A Dutch postcode might be ",[14,43,44],{},"1012ab"," or ",[14,47,48],{},"1012 AB",". Mail merges, delivery integrations and duplicate detection all need one correct format per country, and the format depends on the country — so the cleaning has to look at the country column too. This page builds a postcode normaliser with pandas: read the codes as text, restore lost leading zeros, apply each country's formatting rule, validate against its pattern, and report everything that still does not look right. It belongs to ",[51,52,54],"a",{"href":53},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002F","Cleaning Excel Data with pandas",".",[57,58,60],"h2",{"id":59},"prerequisites","Prerequisites",[62,63,68],"pre",{"className":64,"code":65,"language":66,"meta":67,"style":67},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[14,69,70],{"__ignoreMap":67},[71,72,75,79,83,86],"span",{"class":73,"line":74},"line",1,[71,76,78],{"class":77},"sMTad","pip",[71,80,82],{"class":81},"srMev"," install",[71,84,85],{"class":81}," pandas",[71,87,88],{"class":81}," openpyxl\n",[10,90,91,94],{},[14,92,93],{},"customers.xlsx"," holds thirteen customers in the US, Canada, the UK, Germany, France and the Netherlands. Several postcodes were stored as numbers and lost their leading zeros, UK and Dutch codes are in mixed case with missing or doubled spaces, one US code is a ZIP+4, and two values are genuinely wrong: a four-digit US ZIP and an incomplete UK postcode.",[57,96,98],{"id":97},"step-1-read-postcodes-as-text","Step 1 — Read postcodes as text",[10,100,101],{},"The first rule of postcodes: never let them be numbers. Read the column as text so nothing more is lost:",[62,103,107],{"className":104,"code":105,"language":106,"meta":67,"style":67},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"customers.xlsx\", dtype={\"Postcode\": str})\nprint(df[[\"Country\", \"Postcode\"]].to_string(index=False))\n","python",[14,108,109,125,132,171],{"__ignoreMap":67},[71,110,111,115,119,122],{"class":73,"line":74},[71,112,114],{"class":113},"s-kum","import",[71,116,118],{"class":117},"skGVy"," pandas ",[71,120,121],{"class":113},"as",[71,123,124],{"class":117}," pd\n",[71,126,128],{"class":73,"line":127},2,[71,129,131],{"emptyLinePlaceholder":130},true,"\n",[71,133,135,138,141,144,147,149,153,155,158,161,164,168],{"class":73,"line":134},3,[71,136,137],{"class":117},"df ",[71,139,140],{"class":113},"=",[71,142,143],{"class":117}," pd.read_excel(",[71,145,146],{"class":81},"\"customers.xlsx\"",[71,148,33],{"class":117},[71,150,152],{"class":151},"sa561","dtype",[71,154,140],{"class":113},[71,156,157],{"class":117},"{",[71,159,160],{"class":81},"\"Postcode\"",[71,162,163],{"class":117},": ",[71,165,167],{"class":166},"sP0c6","str",[71,169,170],{"class":117},"})\n",[71,172,174,177,180,183,185,187,190,193,195,198],{"class":73,"line":173},4,[71,175,176],{"class":166},"print",[71,178,179],{"class":117},"(df[[",[71,181,182],{"class":81},"\"Country\"",[71,184,33],{"class":117},[71,186,160],{"class":81},[71,188,189],{"class":117},"]].to_string(",[71,191,192],{"class":151},"index",[71,194,140],{"class":113},[71,196,197],{"class":166},"False",[71,199,200],{"class":117},"))\n",[10,202,203,204,207,208,37,211,214,215,37,218,221,222,55],{},"With ",[14,205,206],{},"dtype={\"Postcode\": str}",", pandas reads whatever is in each cell as text. That cannot bring back zeros that are already gone — the cells holding the numbers 2134 and 1067 come back as ",[14,209,210],{},"\"2134\"",[14,212,213],{},"\"1067\""," — but it stops the text values ",[14,216,217],{},"02108-1234",[14,219,220],{},"69002"," being turned into numbers on the way in, and it means the cleaning code always works with strings. Leading zeros in Excel generally are covered in ",[51,223,225],{"href":224},"\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",[57,227,229],{"id":228},"step-2-basic-clean-up-for-every-country","Step 2 — Basic clean-up for every country",[10,231,232,233,236],{},"Before applying country rules, normalise the things that are wrong everywhere: case, surrounding spaces, repeated internal spaces, and a stray ",[14,234,235],{},".0"," from a number that passed through a float:",[62,238,240],{"className":104,"code":239,"language":106,"meta":67,"style":67},"import re\n\ndef basic(code):\n    if code is None or pd.isna(code):\n        return \"\"\n    code = str(code).strip().upper()\n    code = re.sub(r\"\\.0$\", \"\", code)          # 2134.0 → 2134\n    return re.sub(r\"\\s+\", \" \", code)\n\ndf[\"pc\"] = df[\"Postcode\"].map(basic)\nprint(df[[\"Country\", \"Postcode\", \"pc\"]].to_string(index=False))\n",[14,241,242,249,253,265,285,294,308,348,376,381,403],{"__ignoreMap":67},[71,243,244,246],{"class":73,"line":74},[71,245,114],{"class":113},[71,247,248],{"class":117}," re\n",[71,250,251],{"class":73,"line":127},[71,252,131],{"emptyLinePlaceholder":130},[71,254,255,258,262],{"class":73,"line":134},[71,256,257],{"class":113},"def",[71,259,261],{"class":260},"s_Opv"," basic",[71,263,264],{"class":117},"(code):\n",[71,266,267,270,273,276,279,282],{"class":73,"line":173},[71,268,269],{"class":113},"    if",[71,271,272],{"class":117}," code ",[71,274,275],{"class":113},"is",[71,277,278],{"class":166}," None",[71,280,281],{"class":113}," or",[71,283,284],{"class":117}," pd.isna(code):\n",[71,286,288,291],{"class":73,"line":287},5,[71,289,290],{"class":113},"        return",[71,292,293],{"class":81}," \"\"\n",[71,295,297,300,302,305],{"class":73,"line":296},6,[71,298,299],{"class":117},"    code ",[71,301,140],{"class":113},[71,303,304],{"class":166}," str",[71,306,307],{"class":117},"(code).strip().upper()\n",[71,309,311,313,315,318,321,324,328,331,334,336,338,341,344],{"class":73,"line":310},7,[71,312,299],{"class":117},[71,314,140],{"class":113},[71,316,317],{"class":117}," re.sub(",[71,319,320],{"class":113},"r",[71,322,323],{"class":81},"\"",[71,325,327],{"class":326},"s_b0D","\\.",[71,329,330],{"class":81},"0",[71,332,333],{"class":166},"$",[71,335,323],{"class":81},[71,337,33],{"class":117},[71,339,340],{"class":81},"\"\"",[71,342,343],{"class":117},", code)          ",[71,345,347],{"class":346},"s-wDw","# 2134.0 → 2134\n",[71,349,351,354,356,358,360,363,366,368,370,373],{"class":73,"line":350},8,[71,352,353],{"class":113},"    return",[71,355,317],{"class":117},[71,357,320],{"class":113},[71,359,323],{"class":81},[71,361,362],{"class":166},"\\s",[71,364,365],{"class":113},"+",[71,367,323],{"class":81},[71,369,33],{"class":117},[71,371,372],{"class":81},"\" \"",[71,374,375],{"class":117},", code)\n",[71,377,379],{"class":73,"line":378},9,[71,380,131],{"emptyLinePlaceholder":130},[71,382,384,387,390,393,395,398,400],{"class":73,"line":383},10,[71,385,386],{"class":117},"df[",[71,388,389],{"class":81},"\"pc\"",[71,391,392],{"class":117},"] ",[71,394,140],{"class":113},[71,396,397],{"class":117}," df[",[71,399,160],{"class":81},[71,401,402],{"class":117},"].map(basic)\n",[71,404,406,408,410,412,414,416,418,420,422,424,426,428],{"class":73,"line":405},11,[71,407,176],{"class":166},[71,409,179],{"class":117},[71,411,182],{"class":81},[71,413,33],{"class":117},[71,415,160],{"class":81},[71,417,33],{"class":117},[71,419,389],{"class":81},[71,421,189],{"class":117},[71,423,192],{"class":151},[71,425,140],{"class":113},[71,427,197],{"class":166},[71,429,200],{"class":117},[10,431,432,433,25,436,439,440,442],{},"Upper case is the standard for every format that contains letters, and collapsing whitespace turns ",[14,434,435],{},"LS1  4AP",[14,437,438],{},"LS1 4AP",". The ",[14,441,235],{}," rule handles codes that were read as floats somewhere upstream — a common accident when a CSV export goes through another tool first. After this step the remaining work is country-specific.",[57,444,446],{"id":445},"step-3-country-rules-zeros-spaces-and-hyphens","Step 3 — Country rules: zeros, spaces and hyphens",[10,448,449],{},"Each country gets a small function that formats a cleaned code and a pattern that validates the result:",[62,451,453],{"className":104,"code":452,"language":106,"meta":67,"style":67},"def us(code):\n    digits = code.replace(\" \", \"\")\n    m = re.fullmatch(r\"(\\d{3,5})(?:-?(\\d{4}))?\", digits)\n    if not m:\n        return code\n    zip5 = m.group(1).zfill(5) if len(m.group(1)) >= 4 else m.group(1)\n    return zip5 + (f\"-{m.group(2)}\" if m.group(2) else \"\")\n\ndef gb(code):\n    compact = code.replace(\" \", \"\")\n    return f\"{compact[:-3]} {compact[-3:]}\" if len(compact) >= 5 else compact\n\ndef nl(code):\n    compact = code.replace(\" \", \"\")\n    return f\"{compact[:4]} {compact[4:]}\" if len(compact) == 6 else compact\n\ndef ca(code):\n    compact = code.replace(\" \", \"\")\n    return f\"{compact[:3]} {compact[3:]}\" if len(compact) == 6 else compact\n\nRULES = {\n    \"US\": (us, r\"\\d{5}(-\\d{4})?\"),\n    \"GB\": (gb, r\"[A-Z]{1,2}\\d[A-Z\\d]? \\d[A-Z]{2}\"),\n    \"NL\": (nl, r\"[1-9]\\d{3} [A-Z]{2}\"),\n    \"CA\": (ca, r\"[A-Z]\\d[A-Z] \\d[A-Z]\\d\"),\n    \"DE\": (lambda c: c.zfill(5) if c.isdigit() and len(c) == 4 else c, r\"\\d{5}\"),\n    \"FR\": (lambda c: c.zfill(5) if c.isdigit() and len(c) == 4 else c, r\"\\d{5}\"),\n}\n\ndef normalise(row):\n    fmt, pattern = RULES.get(row[\"Country\"], (lambda c: c, r\".+\"))\n    fixed = fmt(row[\"pc\"])\n    return pd.Series({\"Postcode clean\": fixed, \"Valid\": bool(re.fullmatch(pattern, fixed))})\n\ndf[[\"Postcode clean\", \"Valid\"]] = df.apply(normalise, axis=1)\nprint(df[[\"Customer\", \"Country\", \"Postcode\", \"Postcode clean\", \"Valid\"]].to_string(index=False))\n",[14,454,455,464,483,527,537,544,595,647,651,660,677,735,740,750,767,815,820,830,847,892,897,909,946,994,1031,1065,1118,1164,1170,1175,1186,1222,1238,1263,1268,1297],{"__ignoreMap":67},[71,456,457,459,462],{"class":73,"line":74},[71,458,257],{"class":113},[71,460,461],{"class":260}," us",[71,463,264],{"class":117},[71,465,466,469,471,474,476,478,480],{"class":73,"line":127},[71,467,468],{"class":117},"    digits ",[71,470,140],{"class":113},[71,472,473],{"class":117}," code.replace(",[71,475,372],{"class":81},[71,477,33],{"class":117},[71,479,340],{"class":81},[71,481,482],{"class":117},")\n",[71,484,485,488,490,493,495,497,500,503,506,509,512,514,517,520,522,524],{"class":73,"line":134},[71,486,487],{"class":117},"    m ",[71,489,140],{"class":113},[71,491,492],{"class":117}," re.fullmatch(",[71,494,320],{"class":113},[71,496,323],{"class":81},[71,498,499],{"class":166},"(\\d",[71,501,502],{"class":113},"{3,5}",[71,504,505],{"class":166},")(?:",[71,507,508],{"class":81},"-",[71,510,511],{"class":113},"?",[71,513,499],{"class":166},[71,515,516],{"class":113},"{4}",[71,518,519],{"class":166},"))",[71,521,511],{"class":113},[71,523,323],{"class":81},[71,525,526],{"class":117},", digits)\n",[71,528,529,531,534],{"class":73,"line":173},[71,530,269],{"class":113},[71,532,533],{"class":113}," not",[71,535,536],{"class":117}," m:\n",[71,538,539,541],{"class":73,"line":287},[71,540,290],{"class":113},[71,542,543],{"class":117}," code\n",[71,545,546,549,551,554,557,560,563,566,569,572,575,577,580,583,586,589,591,593],{"class":73,"line":296},[71,547,548],{"class":117},"    zip5 ",[71,550,140],{"class":113},[71,552,553],{"class":117}," m.group(",[71,555,556],{"class":166},"1",[71,558,559],{"class":117},").zfill(",[71,561,562],{"class":166},"5",[71,564,565],{"class":117},") ",[71,567,568],{"class":113},"if",[71,570,571],{"class":166}," len",[71,573,574],{"class":117},"(m.group(",[71,576,556],{"class":166},[71,578,579],{"class":117},")) ",[71,581,582],{"class":113},">=",[71,584,585],{"class":166}," 4",[71,587,588],{"class":113}," else",[71,590,553],{"class":117},[71,592,556],{"class":166},[71,594,482],{"class":117},[71,596,597,599,602,604,607,610,613,616,619,622,625,628,630,633,635,637,639,642,645],{"class":73,"line":310},[71,598,353],{"class":113},[71,600,601],{"class":117}," zip5 ",[71,603,365],{"class":113},[71,605,606],{"class":117}," (",[71,608,609],{"class":113},"f",[71,611,612],{"class":81},"\"-",[71,614,157],{"class":615},"sSjpA",[71,617,618],{"class":117},"m.group(",[71,620,621],{"class":166},"2",[71,623,624],{"class":117},")",[71,626,627],{"class":615},"}",[71,629,323],{"class":81},[71,631,632],{"class":113}," if",[71,634,553],{"class":117},[71,636,621],{"class":166},[71,638,565],{"class":117},[71,640,641],{"class":113},"else",[71,643,644],{"class":81}," \"\"",[71,646,482],{"class":117},[71,648,649],{"class":73,"line":350},[71,650,131],{"emptyLinePlaceholder":130},[71,652,653,655,658],{"class":73,"line":378},[71,654,257],{"class":113},[71,656,657],{"class":260}," gb",[71,659,264],{"class":117},[71,661,662,665,667,669,671,673,675],{"class":73,"line":383},[71,663,664],{"class":117},"    compact ",[71,666,140],{"class":113},[71,668,473],{"class":117},[71,670,372],{"class":81},[71,672,33],{"class":117},[71,674,340],{"class":81},[71,676,482],{"class":117},[71,678,679,681,684,686,688,691,693,696,699,701,704,707,709,711,714,716,718,720,722,725,727,730,732],{"class":73,"line":405},[71,680,353],{"class":113},[71,682,683],{"class":113}," f",[71,685,323],{"class":81},[71,687,157],{"class":615},[71,689,690],{"class":117},"compact[:",[71,692,508],{"class":113},[71,694,695],{"class":166},"3",[71,697,698],{"class":117},"]",[71,700,627],{"class":615},[71,702,703],{"class":615}," {",[71,705,706],{"class":117},"compact[",[71,708,508],{"class":113},[71,710,695],{"class":166},[71,712,713],{"class":117},":]",[71,715,627],{"class":615},[71,717,323],{"class":81},[71,719,632],{"class":113},[71,721,571],{"class":166},[71,723,724],{"class":117},"(compact) ",[71,726,582],{"class":113},[71,728,729],{"class":166}," 5",[71,731,588],{"class":113},[71,733,734],{"class":117}," compact\n",[71,736,738],{"class":73,"line":737},12,[71,739,131],{"emptyLinePlaceholder":130},[71,741,743,745,748],{"class":73,"line":742},13,[71,744,257],{"class":113},[71,746,747],{"class":260}," nl",[71,749,264],{"class":117},[71,751,753,755,757,759,761,763,765],{"class":73,"line":752},14,[71,754,664],{"class":117},[71,756,140],{"class":113},[71,758,473],{"class":117},[71,760,372],{"class":81},[71,762,33],{"class":117},[71,764,340],{"class":81},[71,766,482],{"class":117},[71,768,770,772,774,776,778,780,783,785,787,789,791,793,795,797,799,801,803,805,808,811,813],{"class":73,"line":769},15,[71,771,353],{"class":113},[71,773,683],{"class":113},[71,775,323],{"class":81},[71,777,157],{"class":615},[71,779,690],{"class":117},[71,781,782],{"class":166},"4",[71,784,698],{"class":117},[71,786,627],{"class":615},[71,788,703],{"class":615},[71,790,706],{"class":117},[71,792,782],{"class":166},[71,794,713],{"class":117},[71,796,627],{"class":615},[71,798,323],{"class":81},[71,800,632],{"class":113},[71,802,571],{"class":166},[71,804,724],{"class":117},[71,806,807],{"class":113},"==",[71,809,810],{"class":166}," 6",[71,812,588],{"class":113},[71,814,734],{"class":117},[71,816,818],{"class":73,"line":817},16,[71,819,131],{"emptyLinePlaceholder":130},[71,821,823,825,828],{"class":73,"line":822},17,[71,824,257],{"class":113},[71,826,827],{"class":260}," ca",[71,829,264],{"class":117},[71,831,833,835,837,839,841,843,845],{"class":73,"line":832},18,[71,834,664],{"class":117},[71,836,140],{"class":113},[71,838,473],{"class":117},[71,840,372],{"class":81},[71,842,33],{"class":117},[71,844,340],{"class":81},[71,846,482],{"class":117},[71,848,850,852,854,856,858,860,862,864,866,868,870,872,874,876,878,880,882,884,886,888,890],{"class":73,"line":849},19,[71,851,353],{"class":113},[71,853,683],{"class":113},[71,855,323],{"class":81},[71,857,157],{"class":615},[71,859,690],{"class":117},[71,861,695],{"class":166},[71,863,698],{"class":117},[71,865,627],{"class":615},[71,867,703],{"class":615},[71,869,706],{"class":117},[71,871,695],{"class":166},[71,873,713],{"class":117},[71,875,627],{"class":615},[71,877,323],{"class":81},[71,879,632],{"class":113},[71,881,571],{"class":166},[71,883,724],{"class":117},[71,885,807],{"class":113},[71,887,810],{"class":166},[71,889,588],{"class":113},[71,891,734],{"class":117},[71,893,895],{"class":73,"line":894},20,[71,896,131],{"emptyLinePlaceholder":130},[71,898,900,903,906],{"class":73,"line":899},21,[71,901,902],{"class":166},"RULES",[71,904,905],{"class":113}," =",[71,907,908],{"class":117}," {\n",[71,910,912,915,918,920,922,925,928,931,933,935,937,939,941,943],{"class":73,"line":911},22,[71,913,914],{"class":81},"    \"US\"",[71,916,917],{"class":117},": (us, ",[71,919,320],{"class":113},[71,921,323],{"class":81},[71,923,924],{"class":166},"\\d",[71,926,927],{"class":113},"{5}",[71,929,930],{"class":166},"(",[71,932,508],{"class":81},[71,934,924],{"class":166},[71,936,516],{"class":113},[71,938,624],{"class":166},[71,940,511],{"class":113},[71,942,323],{"class":81},[71,944,945],{"class":117},"),\n",[71,947,949,952,955,957,959,962,965,967,970,973,975,978,980,983,985,987,990,992],{"class":73,"line":948},23,[71,950,951],{"class":81},"    \"GB\"",[71,953,954],{"class":117},": (gb, ",[71,956,320],{"class":113},[71,958,323],{"class":81},[71,960,961],{"class":166},"[",[71,963,964],{"class":615},"A-Z",[71,966,698],{"class":166},[71,968,969],{"class":113},"{1,2}",[71,971,972],{"class":166},"\\d[",[71,974,964],{"class":615},[71,976,977],{"class":166},"\\d]",[71,979,511],{"class":113},[71,981,982],{"class":166}," \\d[",[71,984,964],{"class":615},[71,986,698],{"class":166},[71,988,989],{"class":113},"{2}",[71,991,323],{"class":81},[71,993,945],{"class":117},[71,995,997,1000,1003,1005,1007,1009,1012,1015,1018,1021,1023,1025,1027,1029],{"class":73,"line":996},24,[71,998,999],{"class":81},"    \"NL\"",[71,1001,1002],{"class":117},": (nl, ",[71,1004,320],{"class":113},[71,1006,323],{"class":81},[71,1008,961],{"class":166},[71,1010,1011],{"class":615},"1-9",[71,1013,1014],{"class":166},"]\\d",[71,1016,1017],{"class":113},"{3}",[71,1019,1020],{"class":166}," [",[71,1022,964],{"class":615},[71,1024,698],{"class":166},[71,1026,989],{"class":113},[71,1028,323],{"class":81},[71,1030,945],{"class":117},[71,1032,1034,1037,1040,1042,1044,1046,1048,1051,1053,1055,1057,1059,1061,1063],{"class":73,"line":1033},25,[71,1035,1036],{"class":81},"    \"CA\"",[71,1038,1039],{"class":117},": (ca, ",[71,1041,320],{"class":113},[71,1043,323],{"class":81},[71,1045,961],{"class":166},[71,1047,964],{"class":615},[71,1049,1050],{"class":166},"]\\d[",[71,1052,964],{"class":615},[71,1054,698],{"class":166},[71,1056,982],{"class":166},[71,1058,964],{"class":615},[71,1060,1014],{"class":166},[71,1062,323],{"class":81},[71,1064,945],{"class":117},[71,1066,1068,1071,1074,1077,1080,1082,1084,1086,1089,1092,1094,1097,1099,1101,1103,1106,1108,1110,1112,1114,1116],{"class":73,"line":1067},26,[71,1069,1070],{"class":81},"    \"DE\"",[71,1072,1073],{"class":117},": (",[71,1075,1076],{"class":113},"lambda",[71,1078,1079],{"class":117}," c: c.zfill(",[71,1081,562],{"class":166},[71,1083,565],{"class":117},[71,1085,568],{"class":113},[71,1087,1088],{"class":117}," c.isdigit() ",[71,1090,1091],{"class":113},"and",[71,1093,571],{"class":166},[71,1095,1096],{"class":117},"(c) ",[71,1098,807],{"class":113},[71,1100,585],{"class":166},[71,1102,588],{"class":113},[71,1104,1105],{"class":117}," c, ",[71,1107,320],{"class":113},[71,1109,323],{"class":81},[71,1111,924],{"class":166},[71,1113,927],{"class":113},[71,1115,323],{"class":81},[71,1117,945],{"class":117},[71,1119,1121,1124,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1160,1162],{"class":73,"line":1120},27,[71,1122,1123],{"class":81},"    \"FR\"",[71,1125,1073],{"class":117},[71,1127,1076],{"class":113},[71,1129,1079],{"class":117},[71,1131,562],{"class":166},[71,1133,565],{"class":117},[71,1135,568],{"class":113},[71,1137,1088],{"class":117},[71,1139,1091],{"class":113},[71,1141,571],{"class":166},[71,1143,1096],{"class":117},[71,1145,807],{"class":113},[71,1147,585],{"class":166},[71,1149,588],{"class":113},[71,1151,1105],{"class":117},[71,1153,320],{"class":113},[71,1155,323],{"class":81},[71,1157,924],{"class":166},[71,1159,927],{"class":113},[71,1161,323],{"class":81},[71,1163,945],{"class":117},[71,1165,1167],{"class":73,"line":1166},28,[71,1168,1169],{"class":117},"}\n",[71,1171,1173],{"class":73,"line":1172},29,[71,1174,131],{"emptyLinePlaceholder":130},[71,1176,1178,1180,1183],{"class":73,"line":1177},30,[71,1179,257],{"class":113},[71,1181,1182],{"class":260}," normalise",[71,1184,1185],{"class":117},"(row):\n",[71,1187,1189,1192,1194,1197,1200,1202,1205,1207,1210,1212,1214,1216,1218,1220],{"class":73,"line":1188},31,[71,1190,1191],{"class":117},"    fmt, pattern ",[71,1193,140],{"class":113},[71,1195,1196],{"class":166}," RULES",[71,1198,1199],{"class":117},".get(row[",[71,1201,182],{"class":81},[71,1203,1204],{"class":117},"], (",[71,1206,1076],{"class":113},[71,1208,1209],{"class":117}," c: c, ",[71,1211,320],{"class":113},[71,1213,323],{"class":81},[71,1215,55],{"class":166},[71,1217,365],{"class":113},[71,1219,323],{"class":81},[71,1221,200],{"class":117},[71,1223,1225,1228,1230,1233,1235],{"class":73,"line":1224},32,[71,1226,1227],{"class":117},"    fixed ",[71,1229,140],{"class":113},[71,1231,1232],{"class":117}," fmt(row[",[71,1234,389],{"class":81},[71,1236,1237],{"class":117},"])\n",[71,1239,1241,1243,1246,1249,1252,1255,1257,1260],{"class":73,"line":1240},33,[71,1242,353],{"class":113},[71,1244,1245],{"class":117}," pd.Series({",[71,1247,1248],{"class":81},"\"Postcode clean\"",[71,1250,1251],{"class":117},": fixed, ",[71,1253,1254],{"class":81},"\"Valid\"",[71,1256,163],{"class":117},[71,1258,1259],{"class":166},"bool",[71,1261,1262],{"class":117},"(re.fullmatch(pattern, fixed))})\n",[71,1264,1266],{"class":73,"line":1265},34,[71,1267,131],{"emptyLinePlaceholder":130},[71,1269,1271,1274,1276,1278,1280,1283,1285,1288,1291,1293,1295],{"class":73,"line":1270},35,[71,1272,1273],{"class":117},"df[[",[71,1275,1248],{"class":81},[71,1277,33],{"class":117},[71,1279,1254],{"class":81},[71,1281,1282],{"class":117},"]] ",[71,1284,140],{"class":113},[71,1286,1287],{"class":117}," df.apply(normalise, ",[71,1289,1290],{"class":151},"axis",[71,1292,140],{"class":113},[71,1294,556],{"class":166},[71,1296,482],{"class":117},[71,1298,1300,1302,1304,1307,1309,1311,1313,1315,1317,1319,1321,1323,1325,1327,1329,1331],{"class":73,"line":1299},36,[71,1301,176],{"class":166},[71,1303,179],{"class":117},[71,1305,1306],{"class":81},"\"Customer\"",[71,1308,33],{"class":117},[71,1310,182],{"class":81},[71,1312,33],{"class":117},[71,1314,160],{"class":81},[71,1316,33],{"class":117},[71,1318,1248],{"class":81},[71,1320,33],{"class":117},[71,1322,1254],{"class":81},[71,1324,189],{"class":117},[71,1326,192],{"class":151},[71,1328,140],{"class":113},[71,1330,197],{"class":166},[71,1332,200],{"class":117},[10,1334,1335,1336,1338,1339,1341,1342,1338,1344,37,1347,1338,1349,1351,1352,1338,1354,1356,1357,1361],{},"US ZIPs that lost a leading zero are padded back to five digits — ",[14,1337,20],{}," becomes ",[14,1340,16],{}," — and ZIP+4 keeps its hyphen. UK postcodes always have their space before the last three characters (the \"inward code\"), whatever the length of the first part, so ",[14,1343,32],{},[14,1345,1346],{},"SW1A 1AA",[14,1348,435],{},[14,1350,438],{},". Dutch codes get a space between the four digits and two letters, Canadian codes between the two halves, and German and French codes are padded to five digits — ",[14,1353,28],{},[14,1355,24],{},", Dresden's real postcode. The patterns check the shape of each result; they confirm a postcode is ",[1358,1359,1360],"em",{},"well formed",", not that it exists.",[1363,1364,1372,1373,1372,1377,1372,1381,1372,1388,1372,1397,1372,1401,1372,1408,1372,1412,1372,1416,1372,1420,1372,1424,1372,1426,1372,1430,1372,1432,1372,1435,1372,1438,1372,1441,1372,1443,1372,1447,1372,1449,1372,1451,1372,1454,1372,1457,1372,1459,1372,1462,1372,1464,1372,1466,1372,1469,1372,1472,1372,1474,1372,1477,1372,1479,1372,1481,1372,1484,1372,1487,1372,1489,1372,1492,1372,1494,1372,1497,1372,1500,1372,1504,1372,1506,1372,1509,1372,1511,1372,1513,1372,1516,1372,1520,1372,1525,1372,1528,1372,1531,1372,1534,1372,1538,1372,1541,1372,1544,1372,1547,1372,1549,1372,1551,1372,1554,1372,1557,1372,1559,1372,1561,1372,1564,1372,1567,1372,1569,1372,1572,1372,1575,1372,1578,1372,1580,1372,1582,1372,1588,1372,1592,1372,1598,1372,1603,1372,1611],"svg",{"viewBox":1365,"role":1366,"ariaLabelledBy":1367,"xmlns":1370,"style":1371},"70 0 619 248","img",[1368,1369],"pc-fix-t","pc-fix-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:619px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[1374,1375,1376],"title",{"id":1368},"Raw postcodes become each country's standard form",[1378,1379,1380],"desc",{"id":1369},"The US ZIP 2134 is padded to 02134, the UK code sw1a1aa becomes SW1A 1AA with the space before the last three characters, the Dutch 1012ab becomes 1012 AB, the Canadian k1a0b1 becomes K1A 0B1 and the German 1067 becomes 01067.",[1382,1383],"rect",{"x":1384,"y":330,"width":1385,"height":1386,"fill":1387},"70","619","248","#ffffff",[1382,1389],{"x":1390,"y":1391,"width":1392,"height":1393,"rx":330,"fill":1394,"stroke":1395,"style":1396},"86.6","40.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[1382,1398],{"x":1399,"y":1391,"width":1400,"height":1393,"rx":330,"fill":1394,"stroke":1395,"style":1396},"118.6","67.8",[1402,1403,1407],"text",{"x":1404,"y":1405,"style":1406},"152.5","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[1382,1409],{"x":1410,"y":1391,"width":1411,"height":1393,"rx":330,"fill":1394,"stroke":1395,"style":1396},"186.4","74.9",[1402,1413,1415],{"x":1414,"y":1405,"style":1406},"223.8","B",[1382,1417],{"x":1390,"y":1418,"width":1392,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1396},"64.0","28.0",[1402,1421,556],{"x":1422,"y":1423,"style":1406},"102.6","83.0",[1382,1425],{"x":1399,"y":1418,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1427,1429],{"x":1404,"y":1423,"style":1428},"font-size:10.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Country",[1382,1431],{"x":1410,"y":1418,"width":1411,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1433,1434],{"x":1414,"y":1423,"style":1428},"Postcode",[1382,1436],{"x":1390,"y":1437,"width":1392,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1396},"92.0",[1402,1439,621],{"x":1422,"y":1440,"style":1406},"111.0",[1382,1442],{"x":1399,"y":1437,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1444,1446],{"x":1404,"y":1440,"style":1445},"font-size:10.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","US",[1382,1448],{"x":1410,"y":1437,"width":1411,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1450,20],{"x":1414,"y":1440,"style":1445},[1382,1452],{"x":1390,"y":1453,"width":1392,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1396},"120.0",[1402,1455,695],{"x":1422,"y":1456,"style":1406},"139.0",[1382,1458],{"x":1399,"y":1453,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1460,1461],{"x":1404,"y":1456,"style":1445},"GB",[1382,1463],{"x":1410,"y":1453,"width":1411,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1465,32],{"x":1414,"y":1456,"style":1445},[1382,1467],{"x":1390,"y":1468,"width":1392,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1396},"148.0",[1402,1470,782],{"x":1422,"y":1471,"style":1406},"167.0",[1382,1473],{"x":1399,"y":1468,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1475,1476],{"x":1404,"y":1471,"style":1445},"NL",[1382,1478],{"x":1410,"y":1468,"width":1411,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1480,44],{"x":1414,"y":1471,"style":1445},[1382,1482],{"x":1390,"y":1483,"width":1392,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1396},"176.0",[1402,1485,562],{"x":1422,"y":1486,"style":1406},"195.0",[1382,1488],{"x":1399,"y":1483,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1490,1491],{"x":1404,"y":1486,"style":1445},"CA",[1382,1493],{"x":1410,"y":1483,"width":1411,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1495,1496],{"x":1414,"y":1486,"style":1445},"k1a0b1",[1382,1498],{"x":1390,"y":1499,"width":1392,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1396},"204.0",[1402,1501,1503],{"x":1422,"y":1502,"style":1406},"223.0","6",[1382,1505],{"x":1399,"y":1499,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1507,1508],{"x":1404,"y":1502,"style":1445},"DE",[1382,1510],{"x":1410,"y":1499,"width":1411,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},[1402,1512,28],{"x":1414,"y":1502,"style":1445},[1382,1514],{"x":1515,"y":1391,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},"526.1",[1402,1517,1429],{"x":1518,"y":1519,"style":1428},"560.0","59.0",[1382,1521],{"x":1522,"y":1391,"width":1523,"height":1419,"rx":330,"fill":1394,"stroke":1395,"style":1524},"594.0","79.5","stroke-width:1.5px",[1402,1526,1434],{"x":1527,"y":1519,"style":1428},"633.7",[1382,1529],{"x":1515,"y":1530,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},"68.0",[1402,1532,1446],{"x":1518,"y":1533,"style":1445},"87.0",[1382,1535],{"x":1522,"y":1530,"width":1523,"height":1419,"rx":330,"fill":1536,"stroke":1537,"style":1524},"#d9f4f1","var(--teal,#0f9488)",[1402,1539,16],{"x":1527,"y":1533,"style":1540},"font-size:10.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[1382,1542],{"x":1515,"y":1543,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},"96.0",[1402,1545,1461],{"x":1518,"y":1546,"style":1445},"115.0",[1382,1548],{"x":1522,"y":1543,"width":1523,"height":1419,"rx":330,"fill":1536,"stroke":1537,"style":1524},[1402,1550,1346],{"x":1527,"y":1546,"style":1540},[1382,1552],{"x":1515,"y":1553,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},"124.0",[1402,1555,1476],{"x":1518,"y":1556,"style":1445},"143.0",[1382,1558],{"x":1522,"y":1553,"width":1523,"height":1419,"rx":330,"fill":1536,"stroke":1537,"style":1524},[1402,1560,48],{"x":1527,"y":1556,"style":1540},[1382,1562],{"x":1515,"y":1563,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},"152.0",[1402,1565,1491],{"x":1518,"y":1566,"style":1445},"171.0",[1382,1568],{"x":1522,"y":1563,"width":1523,"height":1419,"rx":330,"fill":1536,"stroke":1537,"style":1524},[1402,1570,1571],{"x":1527,"y":1566,"style":1540},"K1A 0B1",[1382,1573],{"x":1515,"y":1574,"width":1400,"height":1419,"rx":330,"fill":1387,"stroke":1395,"style":1396},"180.0",[1402,1576,1508],{"x":1518,"y":1577,"style":1445},"199.0",[1382,1579],{"x":1522,"y":1574,"width":1523,"height":1419,"rx":330,"fill":1536,"stroke":1537,"style":1524},[1402,1581,24],{"x":1527,"y":1577,"style":1540},[1402,1583,1587],{"x":1584,"y":1585,"style":1586},"173.9","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","as stored",[1402,1589,1591],{"x":1590,"y":1585,"style":1586},"599.8","normalised",[73,1593],{"x1":1594,"y1":1553,"x2":1595,"y2":1553,"stroke":1596,"style":1597},"271.2","512.1","var(--brand,#5b5cf0)","stroke-width:2px",[1599,1600],"polygon",{"points":1601,"fill":1602},"516.1,124.0 507.1,129.0 507.1,119.0","#5b5cf0",[1382,1604],{"x":1605,"y":1606,"width":1607,"height":1608,"rx":1609,"fill":1387,"stroke":1395,"style":1610},"303.7","91.0","179.9","22.0","11","stroke-width:1.2px",[1402,1612,1616],{"x":1613,"y":1614,"style":1615},"393.7","106.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","country rule + pattern",[57,1618,1620],{"id":1619},"step-4-report-what-is-still-wrong","Step 4 — Report what is still wrong",[10,1622,1623],{},"The two values that fail validation need a person, not more code:",[62,1625,1627],{"className":104,"code":1626,"language":106,"meta":67,"style":67},"bad = df.loc[~df[\"Valid\"], [\"Customer\", \"Country\", \"Postcode\", \"Postcode clean\"]]\nprint(bad.to_string(index=False))\nprint(df.groupby(\"Country\")[\"Valid\"].agg([\"sum\", \"count\"]).rename(columns={\"sum\": \"valid\", \"count\": \"rows\"}).to_string())\n",[14,1628,1629,1666,1681],{"__ignoreMap":67},[71,1630,1631,1634,1636,1639,1642,1644,1646,1649,1651,1653,1655,1657,1659,1661,1663],{"class":73,"line":74},[71,1632,1633],{"class":117},"bad ",[71,1635,140],{"class":113},[71,1637,1638],{"class":117}," df.loc[",[71,1640,1641],{"class":113},"~",[71,1643,386],{"class":117},[71,1645,1254],{"class":81},[71,1647,1648],{"class":117},"], [",[71,1650,1306],{"class":81},[71,1652,33],{"class":117},[71,1654,182],{"class":81},[71,1656,33],{"class":117},[71,1658,160],{"class":81},[71,1660,33],{"class":117},[71,1662,1248],{"class":81},[71,1664,1665],{"class":117},"]]\n",[71,1667,1668,1670,1673,1675,1677,1679],{"class":73,"line":127},[71,1669,176],{"class":166},[71,1671,1672],{"class":117},"(bad.to_string(",[71,1674,192],{"class":151},[71,1676,140],{"class":113},[71,1678,197],{"class":166},[71,1680,200],{"class":117},[71,1682,1683,1685,1688,1690,1693,1695,1698,1701,1703,1706,1709,1712,1714,1716,1718,1720,1723,1725,1727,1729,1732],{"class":73,"line":134},[71,1684,176],{"class":166},[71,1686,1687],{"class":117},"(df.groupby(",[71,1689,182],{"class":81},[71,1691,1692],{"class":117},")[",[71,1694,1254],{"class":81},[71,1696,1697],{"class":117},"].agg([",[71,1699,1700],{"class":81},"\"sum\"",[71,1702,33],{"class":117},[71,1704,1705],{"class":81},"\"count\"",[71,1707,1708],{"class":117},"]).rename(",[71,1710,1711],{"class":151},"columns",[71,1713,140],{"class":113},[71,1715,157],{"class":117},[71,1717,1700],{"class":81},[71,1719,163],{"class":117},[71,1721,1722],{"class":81},"\"valid\"",[71,1724,33],{"class":117},[71,1726,1705],{"class":81},[71,1728,163],{"class":117},[71,1730,1731],{"class":81},"\"rows\"",[71,1733,1734],{"class":117},"}).to_string())\n",[10,1736,1737,1740,1741,1744,1745,1748],{},[14,1738,1739],{},"1120"," for a Brooklyn customer is padded to ",[14,1742,1743],{},"01120"," — a well-formed ZIP code, but one in Massachusetts, not New York — and is flagged here only if you add a state-level check; as it stands the rule accepts it, which is worth knowing. The incomplete UK code ",[14,1746,1747],{},"M1 1A"," fails the pattern and is listed. This is the honest limit of format rules: they catch malformed codes, and padding can make a wrong code look right. If accuracy matters, validate against a postcode database or an address API — the UK's Postcode Address File, USPS address validation, or a commercial service — for the rows that matter. A per-country summary of valid rows shows at a glance where the data quality problems are.",[10,1750,1751,1752,33,1754,37,1757,1759],{},"Normalised postcodes also pay off as a join key. Matching customers between two systems, looking up delivery zones or sales territories, or spotting duplicate accounts all compare postcodes, and ",[14,1753,40],{},[14,1755,1756],{},"sw1a 1aa",[14,1758,1346],{}," only match once they are in one canonical form. For joins, some teams also keep a compact version without any spaces in a separate column, which is easier to compare and to index, while the formatted version is used for printing labels and letters.",[1363,1761,1372,1767,1372,1770,1372,1773,1372,1777,1372,1784,1372,1795,1372,1801,1372,1805,1372,1809,1372,1813,1372,1817,1372,1822,1372,1829,1372,1834,1372,1838,1372,1841,1372,1844],{"viewBox":1762,"role":1366,"ariaLabelledBy":1763,"xmlns":1370,"style":1766},"4 0 752 207",[1764,1765],"pc-limits-t","pc-limits-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[1374,1768,1769],{"id":1764},"What format rules can and cannot tell you",[1378,1771,1772],{"id":1765},"Format rules restore zeros, fix spacing and case, and catch malformed codes such as an incomplete UK postcode. They cannot tell whether a well-formed code exists or belongs to the right address; a padded ZIP can look valid and be wrong. That needs a postcode database or address API.",[1382,1774],{"x":782,"y":330,"width":1775,"height":1776,"fill":1387},"752","207",[1382,1778],{"x":1779,"y":1780,"width":1781,"height":1782,"rx":1783,"fill":1536,"stroke":1537,"style":1597},"20.0","16.0","351.0","175.0","14",[1785,1786,1788],"g",{"transform":1787},"translate(182.5,29.0) scale(1.083)",[1789,1790],"path",{"d":1791,"fill":1792,"stroke":1793,"style":1794},"M5 12.5l4.5 4.5L19 7","none","var(--teal-ink,#0b6157)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[1402,1796,1800],{"x":1797,"y":1798,"style":1799},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","format rules can",[73,1802],{"x1":1803,"y1":1606,"x2":1804,"y2":1606,"stroke":1537,"style":1396},"36.0","355.0",[1402,1806,1808],{"x":1797,"y":1440,"style":1807},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","restore lost leading zeros",[1402,1810,1812],{"x":1797,"y":1811,"style":1807},"137.0","fix spaces and case",[1402,1814,1816],{"x":1797,"y":1815,"style":1807},"163.0","catch malformed codes (M1 1A)",[1382,1818],{"x":1819,"y":1780,"width":1781,"height":1782,"rx":1783,"fill":1820,"stroke":1821,"style":1597},"389.0","#fdefd8","var(--gold,#b4740a)",[1785,1823,1825],{"transform":1824},"translate(551.5,29.0) scale(1.083)",[1789,1826],{"d":1827,"fill":1792,"stroke":1828,"style":1794},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[1402,1830,1833],{"x":1831,"y":1798,"style":1832},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","format rules cannot",[73,1835],{"x1":1836,"y1":1606,"x2":1837,"y2":1606,"stroke":1821,"style":1396},"405.0","724.0",[1402,1839,1840],{"x":1831,"y":1440,"style":1807},"confirm the code exists",[1402,1842,1843],{"x":1831,"y":1811,"style":1807},"match code to street or state",[1402,1845,1846],{"x":1831,"y":1815,"style":1807},"spot a padded but wrong ZIP",[57,1848,1850],{"id":1849},"step-5-write-the-codes-so-excel-keeps-them","Step 5 — Write the codes so Excel keeps them",[10,1852,1853],{},"The last trap is writing the cleaned codes back to Excel, which will strip the zeros again if the cells are numbers. Write them as text with a text format:",[62,1855,1857],{"className":104,"code":1856,"language":106,"meta":67,"style":67},"from openpyxl import load_workbook\n\nout = df.drop(columns=[\"pc\"]).rename(columns={\"Postcode\": \"Postcode (original)\"})\nout.to_excel(\"customers_clean.xlsx\", index=False)\nwb = load_workbook(\"customers_clean.xlsx\")\nws = wb.active\ncol = [c.value for c in ws[1]].index(\"Postcode clean\") + 1\nfor r in range(2, ws.max_row + 1):\n    ws.cell(r, col).number_format = \"@\"\nwb.save(\"customers_clean.xlsx\")\ncheck = pd.read_excel(\"customers_clean.xlsx\", dtype={\"Postcode clean\": str})\nprint(check.loc[check[\"Country\"] == \"US\", \"Postcode clean\"].tolist())\n",[14,1858,1859,1872,1876,1911,1929,1943,1953,1989,2016,2026,2035,2062],{"__ignoreMap":67},[71,1860,1861,1864,1867,1869],{"class":73,"line":74},[71,1862,1863],{"class":113},"from",[71,1865,1866],{"class":117}," openpyxl ",[71,1868,114],{"class":113},[71,1870,1871],{"class":117}," load_workbook\n",[71,1873,1874],{"class":73,"line":127},[71,1875,131],{"emptyLinePlaceholder":130},[71,1877,1878,1881,1883,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1909],{"class":73,"line":134},[71,1879,1880],{"class":117},"out ",[71,1882,140],{"class":113},[71,1884,1885],{"class":117}," df.drop(",[71,1887,1711],{"class":151},[71,1889,140],{"class":113},[71,1891,961],{"class":117},[71,1893,389],{"class":81},[71,1895,1708],{"class":117},[71,1897,1711],{"class":151},[71,1899,140],{"class":113},[71,1901,157],{"class":117},[71,1903,160],{"class":81},[71,1905,163],{"class":117},[71,1907,1908],{"class":81},"\"Postcode (original)\"",[71,1910,170],{"class":117},[71,1912,1913,1916,1919,1921,1923,1925,1927],{"class":73,"line":173},[71,1914,1915],{"class":117},"out.to_excel(",[71,1917,1918],{"class":81},"\"customers_clean.xlsx\"",[71,1920,33],{"class":117},[71,1922,192],{"class":151},[71,1924,140],{"class":113},[71,1926,197],{"class":166},[71,1928,482],{"class":117},[71,1930,1931,1934,1936,1939,1941],{"class":73,"line":287},[71,1932,1933],{"class":117},"wb ",[71,1935,140],{"class":113},[71,1937,1938],{"class":117}," load_workbook(",[71,1940,1918],{"class":81},[71,1942,482],{"class":117},[71,1944,1945,1948,1950],{"class":73,"line":296},[71,1946,1947],{"class":117},"ws ",[71,1949,140],{"class":113},[71,1951,1952],{"class":117}," wb.active\n",[71,1954,1955,1958,1960,1963,1966,1969,1972,1975,1977,1980,1982,1984,1986],{"class":73,"line":310},[71,1956,1957],{"class":117},"col ",[71,1959,140],{"class":113},[71,1961,1962],{"class":117}," [c.value ",[71,1964,1965],{"class":113},"for",[71,1967,1968],{"class":117}," c ",[71,1970,1971],{"class":113},"in",[71,1973,1974],{"class":117}," ws[",[71,1976,556],{"class":166},[71,1978,1979],{"class":117},"]].index(",[71,1981,1248],{"class":81},[71,1983,565],{"class":117},[71,1985,365],{"class":113},[71,1987,1988],{"class":166}," 1\n",[71,1990,1991,1993,1996,1998,2001,2003,2005,2008,2010,2013],{"class":73,"line":350},[71,1992,1965],{"class":113},[71,1994,1995],{"class":117}," r ",[71,1997,1971],{"class":113},[71,1999,2000],{"class":166}," range",[71,2002,930],{"class":117},[71,2004,621],{"class":166},[71,2006,2007],{"class":117},", ws.max_row ",[71,2009,365],{"class":113},[71,2011,2012],{"class":166}," 1",[71,2014,2015],{"class":117},"):\n",[71,2017,2018,2021,2023],{"class":73,"line":378},[71,2019,2020],{"class":117},"    ws.cell(r, col).number_format ",[71,2022,140],{"class":113},[71,2024,2025],{"class":81}," \"@\"\n",[71,2027,2028,2031,2033],{"class":73,"line":383},[71,2029,2030],{"class":117},"wb.save(",[71,2032,1918],{"class":81},[71,2034,482],{"class":117},[71,2036,2037,2040,2042,2044,2046,2048,2050,2052,2054,2056,2058,2060],{"class":73,"line":405},[71,2038,2039],{"class":117},"check ",[71,2041,140],{"class":113},[71,2043,143],{"class":117},[71,2045,1918],{"class":81},[71,2047,33],{"class":117},[71,2049,152],{"class":151},[71,2051,140],{"class":113},[71,2053,157],{"class":117},[71,2055,1248],{"class":81},[71,2057,163],{"class":117},[71,2059,167],{"class":166},[71,2061,170],{"class":117},[71,2063,2064,2066,2069,2071,2073,2075,2078,2080,2082],{"class":73,"line":737},[71,2065,176],{"class":166},[71,2067,2068],{"class":117},"(check.loc[check[",[71,2070,182],{"class":81},[71,2072,392],{"class":117},[71,2074,807],{"class":113},[71,2076,2077],{"class":81}," \"US\"",[71,2079,33],{"class":117},[71,2081,1248],{"class":81},[71,2083,2084],{"class":117},"].tolist())\n",[10,2086,2087,2088,2090,2091,2094,2095,55],{},"pandas writes Python strings as text cells, so ",[14,2089,16],{}," is stored with its zero; setting the number format to ",[14,2092,2093],{},"@"," (Text) on the column also stops Excel converting a code if someone edits the cell later. Reading the file back confirms the US codes kept their leading zeros. Keeping the original column next to the clean one preserves what the source sent, which helps when a customer queries their address. Formatting codes for display is covered in ",[51,2096,2098],{"href":2097},"\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",[1363,2100,1372,2105,1372,2108,1372,2111,1372,2114,1372,2119,1372,2127,1372,2133,1372,2138,1372,2143,1372,2146,1372,2150,1372,2157,1372,2162,1372,2165,1372,2168,1372,2171,1372,2174,1372,2180,1372,2185,1372,2188,1372,2192,1372,2195,1372,2198,1372,2203,1372,2208],{"viewBox":2101,"role":1366,"ariaLabelledBy":2102,"xmlns":1370,"style":1766},"4 0 752 125",[2103,2104],"pc-flow-t","pc-flow-d",[1374,2106,2107],{"id":2103},"A postcode clean-up pipeline",[1378,2109,2110],{"id":2104},"Read postcodes as text, apply basic clean-up for case and spaces, apply each country's formatting rule and validation pattern, report the values that fail for manual review, and write the cleaned codes as text cells so Excel keeps their zeros.",[1382,2112],{"x":782,"y":330,"width":1775,"height":2113,"fill":1387},"125",[1382,2115],{"x":1779,"y":1780,"width":2116,"height":2117,"rx":2118,"fill":1394,"stroke":1395,"style":1597},"153.0","93.0","12",[1785,2120,2122],{"transform":2121},"translate(84.5,29.0) scale(1.000)",[1789,2123],{"d":2124,"fill":1792,"stroke":2125,"style":2126},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[1402,2128,2132],{"x":2129,"y":2130,"style":2131},"96.5","75.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Read as text",[1402,2134,2137],{"x":2129,"y":2135,"style":2136},"95.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","dtype=str",[73,2139],{"x1":2140,"y1":2141,"x2":2142,"y2":2141,"stroke":1596,"style":1597},"178.0","62.5","200.0",[1599,2144],{"points":2145,"fill":1602},"204.0,62.5 195.0,67.5 195.0,57.5",[1382,2147],{"x":2148,"y":1780,"width":2116,"height":2117,"rx":2118,"fill":2149,"stroke":1596,"style":1597},"209.0","#f0f4ff",[1785,2151,2153],{"transform":2152},"translate(273.5,29.0) scale(1.000)",[1789,2154],{"d":2155,"fill":1792,"stroke":2156,"style":2126},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7","var(--brand-strong,#4338ca)",[1402,2158,2161],{"x":2159,"y":2130,"style":2160},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Basic clean",[1402,2163,2164],{"x":2159,"y":2135,"style":2136},"case, spaces, .0",[73,2166],{"x1":2167,"y1":2141,"x2":1819,"y2":2141,"stroke":1596,"style":1597},"367.0",[1599,2169],{"points":2170,"fill":1602},"393.0,62.5 384.0,67.5 384.0,57.5",[1382,2172],{"x":2173,"y":1780,"width":2116,"height":2117,"rx":2118,"fill":1536,"stroke":1537,"style":1597},"398.0",[1785,2175,2177],{"transform":2176},"translate(462.5,29.0) scale(1.000)",[1789,2178],{"d":2179,"fill":1792,"stroke":1793,"style":2126},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5",[1402,2181,2184],{"x":2182,"y":2130,"style":2183},"474.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Country rule",[1402,2186,2187],{"x":2182,"y":2135,"style":2136},"zeros, space, pattern",[73,2189],{"x1":2190,"y1":2141,"x2":2191,"y2":2141,"stroke":1596,"style":1597},"556.0","578.0",[1599,2193],{"points":2194,"fill":1602},"582.0,62.5 573.0,67.5 573.0,57.5",[1382,2196],{"x":2197,"y":1780,"width":2116,"height":2117,"rx":2118,"fill":1820,"stroke":1821,"style":1597},"587.0",[1785,2199,2201],{"transform":2200},"translate(651.5,29.0) scale(1.000)",[1789,2202],{"d":1791,"fill":1792,"stroke":1828,"style":2126},[1402,2204,2207],{"x":2205,"y":2130,"style":2206},"663.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Report + write",[1402,2209,2210],{"x":2205,"y":2135,"style":2136},"failures, text cells",[57,2212,2214],{"id":2213},"common-pitfalls","Common pitfalls",[2216,2217,2218,2226,2232,2238,2244],"ul",{},[2219,2220,2221,2225],"li",{},[2222,2223,2224],"strong",{},"Postcodes as numbers."," Leading zeros vanish; read and write them as text.",[2219,2227,2228,2231],{},[2222,2229,2230],{},"One rule for all countries."," Format depends on the country; dispatch on the country column.",[2219,2233,2234,2237],{},[2222,2235,2236],{},"Inserting the UK space after a fixed position."," The space always goes before the last three characters.",[2219,2239,2240,2243],{},[2222,2241,2242],{},"Trusting a well-formed code."," Format rules cannot confirm the code exists or matches the address.",[2219,2245,2246,2249,2250,2252],{},[2222,2247,2248],{},"Writing clean codes back as numbers."," Use text cells with the ",[14,2251,2093],{}," format.",[57,2254,2256],{"id":2255},"conclusion","Conclusion",[10,2258,2259],{},"To normalise postcodes from Excel, read them as text, upper-case and tidy the spacing, then apply a formatting rule and a validation pattern per country: pad US ZIP, German and French codes back to five digits, put the UK space before the last three characters, and format Dutch and Canadian codes with their single space. Report every value that fails its pattern for a person to check, remember that a well-formed code is not necessarily a correct one, and write the cleaned codes back as text cells so Excel cannot strip their zeros again.",[57,2261,2263],{"id":2262},"frequently-asked-questions","Frequently asked questions",[10,2265,2266,2269],{},[2222,2267,2268],{},"How do I restore leading zeros in ZIP codes with pandas?","\nRead the column as text and pad four-digit US ZIP codes back to five digits with str.zfill(5), keeping any ZIP+4 extension after a hyphen.",[10,2271,2272,2275],{},[2222,2273,2274],{},"How do I format UK postcodes correctly in Python?","\nRemove all spaces, upper-case the code, and insert a single space before the last three characters, which form the inward code.",[10,2277,2278,2281],{},[2222,2279,2280],{},"How can I validate postcodes for different countries?","\nUse a regular expression per country, such as five digits for the US and Germany or the outward and inward code pattern for the UK, and check each code against its country's pattern.",[10,2283,2284,2287],{},[2222,2285,2286],{},"How do I stop Excel removing leading zeros when I save postcodes?","\nWrite them as strings, not numbers, and set the column's number format to text with @.",[57,2289,2291],{"id":2290},"related","Related",[2216,2293,2294,2300,2307,2314],{},[2219,2295,2296,2297,2299],{},"Up one level: ",[51,2298,54],{"href":53}," — tidy data before analysis.",[2219,2301,2302,2306],{},[51,2303,2305],{"href":2304},"\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"," — other contact fields.",[2219,2308,2309,2313],{},[51,2310,2312],{"href":2311},"\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"," — making the country column reliable first.",[2219,2315,2316,2320],{},[51,2317,2319],{"href":2318},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-long-id-numbers-losing-digits-in-excel\u002F","Fix Long ID Numbers Losing Digits in Excel"," — another number-that-is-not-a-number problem.",[2322,2323,2324],"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 .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":67,"searchDepth":127,"depth":127,"links":2326},[2327,2328,2329,2330,2331,2332,2333,2334,2335,2336],{"id":59,"depth":127,"text":60},{"id":97,"depth":127,"text":98},{"id":228,"depth":127,"text":229},{"id":445,"depth":127,"text":446},{"id":1619,"depth":127,"text":1620},{"id":1849,"depth":127,"text":1850},{"id":2213,"depth":127,"text":2214},{"id":2255,"depth":127,"text":2256},{"id":2262,"depth":127,"text":2263},{"id":2290,"depth":127,"text":2291},"2026-10-02","Clean the postcode column of an international address list from Excel: restore the leading zeros Excel stripped from US ZIP codes and other numeric postcodes, format UK postcodes with the single space in the right place, handle ZIP+4, Dutch and Canadian formats, validate each value against its country's pattern, and list the ones that need a human.","md",[2341,2343,2345,2347],{"q":2268,"a":2342},"Read the column as text and pad four-digit US ZIP codes back to five digits with str.zfill(5), keeping any ZIP+4 extension after a hyphen.",{"q":2274,"a":2344},"Remove all spaces, upper-case the code, and insert a single space before the last three characters, which form the inward code.",{"q":2280,"a":2346},"Use a regular expression per country, such as five digits for the US and Germany or the outward and inward code pattern for the UK, and check each code against its country's pattern.",{"q":2286,"a":2348},"Write them as strings, not numbers, and set the column's number format to text with @.","normalise postcodes and zip codes with pandas",null,{"breadcrumb":2352},[2353,2356,2359],{"name":2354,"item":2355},"Home","\u002F",{"name":2357,"item":2358},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":2360,"item":53},"Cleaning Excel Data With Pandas: A Production Workflow","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas",{"title":2363,"description":2364},"Normalise Postcodes and ZIP Codes in Excel Data with pandas","Fix postcodes from Excel with pandas: restore lost leading zeros in ZIP codes, format UK postcodes with the correct space, handle ZIP+4, Dutch and Canadian patterns, validate per country and report failures.","normalise-postcodes-and-zip-codes-with-pandas","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fnormalise-postcodes-and-zip-codes-with-pandas\u002Findex",[2368,2369],"data-cleaning","pandas","how-to","Kcr0tpT_PGcro12sq1c6XUuPdq_xsUs5jkagJIY0w_w",[2373,2377],{"title":2374,"path":2375,"stem":2376,"children":-1},"Map Free-Text Job Titles to Standard Categories","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fmap-free-text-job-titles-to-standard-categories","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fmap-free-text-job-titles-to-standard-categories\u002Findex",{"title":2378,"path":2379,"stem":2380,"children":-1},"Pandas: Drop Duplicates From an Excel Column","\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fpandas-drop-duplicates-from-excel-column","advanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fpandas-drop-duplicates-from-excel-column\u002Findex",[2382,2384],{"path":2383,"title":2360},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas",{"path":2385,"title":2357},"\u002Fadvanced-data-transformation-and-cleaning",[],[2388,2391,2394,2397,2400,2403],{"path":2389,"title":2390,"kind":2370},"\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":2392,"title":2393,"kind":2370},"\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":2395,"title":2396,"kind":2370},"\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":2398,"title":2399,"kind":2370},"\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":2401,"title":2402,"kind":2370},"\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":2404,"title":2405,"kind":2370},"\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",1790972080846]