[{"data":1,"prerenderedAt":2514},["ShallowReactive",2],{"doc:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file":3,"surround:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file":2480,"ancestors:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file":2489,"children:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file":2494,"related:\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file":2495},{"id":4,"title":5,"body":6,"dateModified":2447,"datePublished":2447,"description":2448,"extension":2449,"faq":2450,"keyword":2459,"kind":2460,"meta":2461,"navigation":97,"path":2469,"seo":2470,"slug":2473,"stem":2474,"topics":2475,"type":2478,"__hash__":2479},"docs\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file\u002Findex.md","Build a Data Quality Scorecard for an Excel File",{"type":7,"value":8,"toc":2436},"minimark",[9,28,33,62,65,69,432,443,447,1191,1221,1331,1335,1458,1461,1783,1804,1808,2236,2244,2318,2322,2362,2366,2369,2373,2379,2389,2395,2401,2405,2432],[10,11,12,13,17,18,21,22,27],"p",{},"When a team receives the same spreadsheet every month — a supplier's price file, a branch's sales return, a customer extract from another system — the question is not only ",[14,15,16],"em",{},"is this file valid?"," but ",[14,19,20],{},"how good is it, and is it getting better or worse?"," A list of individual errors answers the first; a scorecard answers the second. It measures each column on the standard dimensions of data quality — how complete it is, how many values are valid, whether keys are unique, whether coded values come from the allowed list, whether dates are recent — rolls those into a score per column and an overall score, and puts this month next to last month. That turns a vague \"the data from region X is always a mess\" into a number both sides can track. This page builds such a scorecard with pandas from a small table of rules, scores a customer extract, compares it with the previous one, and writes a colour-coded Excel sheet with examples of failing values. It belongs to ",[23,24,26],"a",{"href":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002F","Validating Excel Data with Python",".",[29,30,32],"h2",{"id":31},"prerequisites","Prerequisites",[34,35,40],"pre",{"className":36,"code":37,"language":38,"meta":39,"style":39},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[41,42,43],"code",{"__ignoreMap":39},[44,45,48,52,56,59],"span",{"class":46,"line":47},"line",1,[44,49,51],{"class":50},"sMTad","pip",[44,53,55],{"class":54},"srMev"," install",[44,57,58],{"class":54}," pandas",[44,60,61],{"class":54}," openpyxl\n",[10,63,64],{},"Two monthly customer extracts, August and September 2026, each with four hundred rows: customer ID, name, email, country code, credit limit and date of last order. September's file is the one being scored; August's provides the comparison.",[29,66,68],{"id":67},"step-1-declare-the-rules-once","Step 1 — Declare the rules once",[34,70,74],{"className":71,"code":72,"language":73,"meta":39,"style":39},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nRULES = {\n    \"Customer ID\":  {\"required\": True, \"pattern\": r\"^C\\d{5}$\", \"unique\": True},\n    \"Name\":         {\"required\": True},\n    \"Email\":        {\"required\": True, \"pattern\": r\"^[^@\\s]+@[^@\\s]+\\.[a-z]{2,}$\"},\n    \"Country\":      {\"required\": True, \"allowed\": {\"GB\", \"IE\", \"FR\", \"DE\"}},\n    \"Credit limit\": {\"required\": True, \"range\": (0, 100_000)},\n    \"Last order\":   {\"required\": False, \"max_age_days\": 730},\n}\nWEIGHTS = {\"completeness\": 0.3, \"validity\": 0.4, \"uniqueness\": 0.3}\nAS_OF = pd.Timestamp(\"2026-09-30\")\n","python",[41,75,76,92,99,112,173,190,264,308,341,369,375,415],{"__ignoreMap":39},[44,77,78,82,86,89],{"class":46,"line":47},[44,79,81],{"class":80},"s-kum","import",[44,83,85],{"class":84},"skGVy"," pandas ",[44,87,88],{"class":80},"as",[44,90,91],{"class":84}," pd\n",[44,93,95],{"class":46,"line":94},2,[44,96,98],{"emptyLinePlaceholder":97},true,"\n",[44,100,102,106,109],{"class":46,"line":101},3,[44,103,105],{"class":104},"sP0c6","RULES",[44,107,108],{"class":80}," =",[44,110,111],{"class":84}," {\n",[44,113,115,118,121,124,127,130,133,136,138,141,144,147,150,153,156,159,161,163,166,168,170],{"class":46,"line":114},4,[44,116,117],{"class":54},"    \"Customer ID\"",[44,119,120],{"class":84},":  {",[44,122,123],{"class":54},"\"required\"",[44,125,126],{"class":84},": ",[44,128,129],{"class":104},"True",[44,131,132],{"class":84},", ",[44,134,135],{"class":54},"\"pattern\"",[44,137,126],{"class":84},[44,139,140],{"class":80},"r",[44,142,143],{"class":54},"\"",[44,145,146],{"class":104},"^",[44,148,149],{"class":54},"C",[44,151,152],{"class":104},"\\d",[44,154,155],{"class":80},"{5}",[44,157,158],{"class":104},"$",[44,160,143],{"class":54},[44,162,132],{"class":84},[44,164,165],{"class":54},"\"unique\"",[44,167,126],{"class":84},[44,169,129],{"class":104},[44,171,172],{"class":84},"},\n",[44,174,176,179,182,184,186,188],{"class":46,"line":175},5,[44,177,178],{"class":54},"    \"Name\"",[44,180,181],{"class":84},":         {",[44,183,123],{"class":54},[44,185,126],{"class":84},[44,187,129],{"class":104},[44,189,172],{"class":84},[44,191,193,196,199,201,203,205,207,209,211,213,215,218,220,224,227,230,232,235,237,239,241,243,247,249,252,255,258,260,262],{"class":46,"line":192},6,[44,194,195],{"class":54},"    \"Email\"",[44,197,198],{"class":84},":        {",[44,200,123],{"class":54},[44,202,126],{"class":84},[44,204,129],{"class":104},[44,206,132],{"class":84},[44,208,135],{"class":54},[44,210,126],{"class":84},[44,212,140],{"class":80},[44,214,143],{"class":54},[44,216,217],{"class":104},"^[",[44,219,146],{"class":80},[44,221,223],{"class":222},"sSjpA","@",[44,225,226],{"class":104},"\\s]",[44,228,229],{"class":80},"+",[44,231,223],{"class":54},[44,233,234],{"class":104},"[",[44,236,146],{"class":80},[44,238,223],{"class":222},[44,240,226],{"class":104},[44,242,229],{"class":80},[44,244,246],{"class":245},"s_b0D","\\.",[44,248,234],{"class":104},[44,250,251],{"class":222},"a-z",[44,253,254],{"class":104},"]",[44,256,257],{"class":80},"{2,}",[44,259,158],{"class":104},[44,261,143],{"class":54},[44,263,172],{"class":84},[44,265,267,270,273,275,277,279,281,284,287,290,292,295,297,300,302,305],{"class":46,"line":266},7,[44,268,269],{"class":54},"    \"Country\"",[44,271,272],{"class":84},":      {",[44,274,123],{"class":54},[44,276,126],{"class":84},[44,278,129],{"class":104},[44,280,132],{"class":84},[44,282,283],{"class":54},"\"allowed\"",[44,285,286],{"class":84},": {",[44,288,289],{"class":54},"\"GB\"",[44,291,132],{"class":84},[44,293,294],{"class":54},"\"IE\"",[44,296,132],{"class":84},[44,298,299],{"class":54},"\"FR\"",[44,301,132],{"class":84},[44,303,304],{"class":54},"\"DE\"",[44,306,307],{"class":84},"}},\n",[44,309,311,314,316,318,320,322,324,327,330,333,335,338],{"class":46,"line":310},8,[44,312,313],{"class":54},"    \"Credit limit\"",[44,315,286],{"class":84},[44,317,123],{"class":54},[44,319,126],{"class":84},[44,321,129],{"class":104},[44,323,132],{"class":84},[44,325,326],{"class":54},"\"range\"",[44,328,329],{"class":84},": (",[44,331,332],{"class":104},"0",[44,334,132],{"class":84},[44,336,337],{"class":104},"100_000",[44,339,340],{"class":84},")},\n",[44,342,344,347,350,352,354,357,359,362,364,367],{"class":46,"line":343},9,[44,345,346],{"class":54},"    \"Last order\"",[44,348,349],{"class":84},":   {",[44,351,123],{"class":54},[44,353,126],{"class":84},[44,355,356],{"class":104},"False",[44,358,132],{"class":84},[44,360,361],{"class":54},"\"max_age_days\"",[44,363,126],{"class":84},[44,365,366],{"class":104},"730",[44,368,172],{"class":84},[44,370,372],{"class":46,"line":371},10,[44,373,374],{"class":84},"}\n",[44,376,378,381,383,386,389,391,394,396,399,401,404,406,409,411,413],{"class":46,"line":377},11,[44,379,380],{"class":104},"WEIGHTS",[44,382,108],{"class":80},[44,384,385],{"class":84}," {",[44,387,388],{"class":54},"\"completeness\"",[44,390,126],{"class":84},[44,392,393],{"class":104},"0.3",[44,395,132],{"class":84},[44,397,398],{"class":54},"\"validity\"",[44,400,126],{"class":84},[44,402,403],{"class":104},"0.4",[44,405,132],{"class":84},[44,407,408],{"class":54},"\"uniqueness\"",[44,410,126],{"class":84},[44,412,393],{"class":104},[44,414,374],{"class":84},[44,416,418,421,423,426,429],{"class":46,"line":417},12,[44,419,420],{"class":104},"AS_OF",[44,422,108],{"class":80},[44,424,425],{"class":84}," pd.Timestamp(",[44,427,428],{"class":54},"\"2026-09-30\"",[44,430,431],{"class":84},")\n",[10,433,434,435,438,439,442],{},"The rules describe what ",[14,436,437],{},"good"," looks like for each column, in one place: whether a value is required, a pattern it must match, a range or a list of allowed values it must fall within, whether it must be unique, and how old a date may be before it counts as stale. Keeping the rules as data rather than as scattered ",[41,440,441],{},"if"," statements means the same scorecard code serves any file — a new file type is a new rules dictionary — and the rules themselves can be reviewed by the people who own the data. The weights decide how much each dimension contributes to a column's score; here validity counts most, because a present but wrong value does more damage than a missing one.",[29,444,446],{"id":445},"step-2-measure-each-column","Step 2 — Measure each column",[34,448,450],{"className":71,"code":449,"language":73,"meta":39,"style":39},"def score_column(s, rule):\n    blank = s.isna() | s.astype(str).str.strip().isin([\"\", \"n\u002Fa\", \"N\u002FA\", \"-\"])\n    present = s[~blank]\n    completeness = 1 - blank.mean()\n    ok = pd.Series(True, index=present.index)\n    if \"pattern\" in rule:\n        ok &= present.astype(str).str.match(rule[\"pattern\"])\n    if \"allowed\" in rule:\n        ok &= present.isin(rule[\"allowed\"])\n    if \"range\" in rule:\n        lo, hi = rule[\"range\"]\n        ok &= pd.to_numeric(present, errors=\"coerce\").between(lo, hi, inclusive=\"right\")\n    if \"max_age_days\" in rule:\n        ok &= (AS_OF - pd.to_datetime(present, errors=\"coerce\")).dt.days \u003C= rule[\"max_age_days\"]\n    validity = ok.mean() if len(present) else 1.0\n    uniqueness = 1 - present.duplicated().mean() if rule.get(\"unique\") else None\n    parts = {\"completeness\": completeness, \"validity\": validity, \"uniqueness\": uniqueness}\n    used = {k: WEIGHTS[k] for k, v in parts.items() if v is not None}     # only the dimensions that apply\n    score = sum(parts[k] * w for k, w in used.items()) \u002F sum(used.values()) * 100\n    failing = present[~ok].astype(str).unique()[:3].tolist() + ([\"(blank)\"] if blank.any() else [])\n    return {\"Completeness\": completeness, \"Validity\": validity, \"Uniqueness\": uniqueness,\n            \"Score\": round(score, 1), \"Examples\": \", \".join(failing[:4])}\n\ndef scorecard(path):\n    df = pd.read_excel(path)\n    rows = {col: score_column(df[col], rule) for col, rule in RULES.items()}\n    card = pd.DataFrame(rows).T\n    return card, round(card[\"Score\"].astype(float).mean(), 1)\n\ncard, overall = scorecard(\"customers_2026-09.xlsx\")\nprint(card.drop(columns=\"Examples\").astype({\"Completeness\": float, \"Validity\": float, \"Uniqueness\": float}).round(3).to_string(na_rep=\"—\"))\nprint(\"overall score:\", overall)\n",[41,451,452,464,508,524,540,563,577,597,608,621,632,647,677,689,724,749,779,804,853,896,944,968,1005,1010,1021,1032,1056,1067,1096,1101,1117,1177],{"__ignoreMap":39},[44,453,454,457,461],{"class":46,"line":47},[44,455,456],{"class":80},"def",[44,458,460],{"class":459},"s_Opv"," score_column",[44,462,463],{"class":84},"(s, rule):\n",[44,465,466,469,472,475,478,481,484,487,490,492,495,497,500,502,505],{"class":46,"line":94},[44,467,468],{"class":84},"    blank ",[44,470,471],{"class":80},"=",[44,473,474],{"class":84}," s.isna() ",[44,476,477],{"class":80},"|",[44,479,480],{"class":84}," s.astype(",[44,482,483],{"class":104},"str",[44,485,486],{"class":84},").str.strip().isin([",[44,488,489],{"class":54},"\"\"",[44,491,132],{"class":84},[44,493,494],{"class":54},"\"n\u002Fa\"",[44,496,132],{"class":84},[44,498,499],{"class":54},"\"N\u002FA\"",[44,501,132],{"class":84},[44,503,504],{"class":54},"\"-\"",[44,506,507],{"class":84},"])\n",[44,509,510,513,515,518,521],{"class":46,"line":101},[44,511,512],{"class":84},"    present ",[44,514,471],{"class":80},[44,516,517],{"class":84}," s[",[44,519,520],{"class":80},"~",[44,522,523],{"class":84},"blank]\n",[44,525,526,529,531,534,537],{"class":46,"line":114},[44,527,528],{"class":84},"    completeness ",[44,530,471],{"class":80},[44,532,533],{"class":104}," 1",[44,535,536],{"class":80}," -",[44,538,539],{"class":84}," blank.mean()\n",[44,541,542,545,547,550,552,554,558,560],{"class":46,"line":175},[44,543,544],{"class":84},"    ok ",[44,546,471],{"class":80},[44,548,549],{"class":84}," pd.Series(",[44,551,129],{"class":104},[44,553,132],{"class":84},[44,555,557],{"class":556},"sa561","index",[44,559,471],{"class":80},[44,561,562],{"class":84},"present.index)\n",[44,564,565,568,571,574],{"class":46,"line":192},[44,566,567],{"class":80},"    if",[44,569,570],{"class":54}," \"pattern\"",[44,572,573],{"class":80}," in",[44,575,576],{"class":84}," rule:\n",[44,578,579,582,585,588,590,593,595],{"class":46,"line":266},[44,580,581],{"class":84},"        ok ",[44,583,584],{"class":80},"&=",[44,586,587],{"class":84}," present.astype(",[44,589,483],{"class":104},[44,591,592],{"class":84},").str.match(rule[",[44,594,135],{"class":54},[44,596,507],{"class":84},[44,598,599,601,604,606],{"class":46,"line":310},[44,600,567],{"class":80},[44,602,603],{"class":54}," \"allowed\"",[44,605,573],{"class":80},[44,607,576],{"class":84},[44,609,610,612,614,617,619],{"class":46,"line":343},[44,611,581],{"class":84},[44,613,584],{"class":80},[44,615,616],{"class":84}," present.isin(rule[",[44,618,283],{"class":54},[44,620,507],{"class":84},[44,622,623,625,628,630],{"class":46,"line":371},[44,624,567],{"class":80},[44,626,627],{"class":54}," \"range\"",[44,629,573],{"class":80},[44,631,576],{"class":84},[44,633,634,637,639,642,644],{"class":46,"line":377},[44,635,636],{"class":84},"        lo, hi ",[44,638,471],{"class":80},[44,640,641],{"class":84}," rule[",[44,643,326],{"class":54},[44,645,646],{"class":84},"]\n",[44,648,649,651,653,656,659,661,664,667,670,672,675],{"class":46,"line":417},[44,650,581],{"class":84},[44,652,584],{"class":80},[44,654,655],{"class":84}," pd.to_numeric(present, ",[44,657,658],{"class":556},"errors",[44,660,471],{"class":80},[44,662,663],{"class":54},"\"coerce\"",[44,665,666],{"class":84},").between(lo, hi, ",[44,668,669],{"class":556},"inclusive",[44,671,471],{"class":80},[44,673,674],{"class":54},"\"right\"",[44,676,431],{"class":84},[44,678,680,682,685,687],{"class":46,"line":679},13,[44,681,567],{"class":80},[44,683,684],{"class":54}," \"max_age_days\"",[44,686,573],{"class":80},[44,688,576],{"class":84},[44,690,692,694,696,699,701,703,706,708,710,712,715,718,720,722],{"class":46,"line":691},14,[44,693,581],{"class":84},[44,695,584],{"class":80},[44,697,698],{"class":84}," (",[44,700,420],{"class":104},[44,702,536],{"class":80},[44,704,705],{"class":84}," pd.to_datetime(present, ",[44,707,658],{"class":556},[44,709,471],{"class":80},[44,711,663],{"class":54},[44,713,714],{"class":84},")).dt.days ",[44,716,717],{"class":80},"\u003C=",[44,719,641],{"class":84},[44,721,361],{"class":54},[44,723,646],{"class":84},[44,725,727,730,732,735,737,740,743,746],{"class":46,"line":726},15,[44,728,729],{"class":84},"    validity ",[44,731,471],{"class":80},[44,733,734],{"class":84}," ok.mean() ",[44,736,441],{"class":80},[44,738,739],{"class":104}," len",[44,741,742],{"class":84},"(present) ",[44,744,745],{"class":80},"else",[44,747,748],{"class":104}," 1.0\n",[44,750,752,755,757,759,761,764,766,769,771,774,776],{"class":46,"line":751},16,[44,753,754],{"class":84},"    uniqueness ",[44,756,471],{"class":80},[44,758,533],{"class":104},[44,760,536],{"class":80},[44,762,763],{"class":84}," present.duplicated().mean() ",[44,765,441],{"class":80},[44,767,768],{"class":84}," rule.get(",[44,770,165],{"class":54},[44,772,773],{"class":84},") ",[44,775,745],{"class":80},[44,777,778],{"class":104}," None\n",[44,780,782,785,787,789,791,794,796,799,801],{"class":46,"line":781},17,[44,783,784],{"class":84},"    parts ",[44,786,471],{"class":80},[44,788,385],{"class":84},[44,790,388],{"class":54},[44,792,793],{"class":84},": completeness, ",[44,795,398],{"class":54},[44,797,798],{"class":84},": validity, ",[44,800,408],{"class":54},[44,802,803],{"class":84},": uniqueness}\n",[44,805,807,810,812,815,817,820,823,826,829,832,834,837,840,843,846,849],{"class":46,"line":806},18,[44,808,809],{"class":84},"    used ",[44,811,471],{"class":80},[44,813,814],{"class":84}," {k: ",[44,816,380],{"class":104},[44,818,819],{"class":84},"[k] ",[44,821,822],{"class":80},"for",[44,824,825],{"class":84}," k, v ",[44,827,828],{"class":80},"in",[44,830,831],{"class":84}," parts.items() ",[44,833,441],{"class":80},[44,835,836],{"class":84}," v ",[44,838,839],{"class":80},"is",[44,841,842],{"class":80}," not",[44,844,845],{"class":104}," None",[44,847,848],{"class":84},"}     ",[44,850,852],{"class":851},"s-wDw","# only the dimensions that apply\n",[44,854,856,859,861,864,867,870,873,875,878,880,883,886,888,891,893],{"class":46,"line":855},19,[44,857,858],{"class":84},"    score ",[44,860,471],{"class":80},[44,862,863],{"class":104}," sum",[44,865,866],{"class":84},"(parts[k] ",[44,868,869],{"class":80},"*",[44,871,872],{"class":84}," w ",[44,874,822],{"class":80},[44,876,877],{"class":84}," k, w ",[44,879,828],{"class":80},[44,881,882],{"class":84}," used.items()) ",[44,884,885],{"class":80},"\u002F",[44,887,863],{"class":104},[44,889,890],{"class":84},"(used.values()) ",[44,892,869],{"class":80},[44,894,895],{"class":104}," 100\n",[44,897,899,902,904,907,909,912,914,917,920,923,925,928,931,934,936,939,941],{"class":46,"line":898},20,[44,900,901],{"class":84},"    failing ",[44,903,471],{"class":80},[44,905,906],{"class":84}," present[",[44,908,520],{"class":80},[44,910,911],{"class":84},"ok].astype(",[44,913,483],{"class":104},[44,915,916],{"class":84},").unique()[:",[44,918,919],{"class":104},"3",[44,921,922],{"class":84},"].tolist() ",[44,924,229],{"class":80},[44,926,927],{"class":84}," ([",[44,929,930],{"class":54},"\"(blank)\"",[44,932,933],{"class":84},"] ",[44,935,441],{"class":80},[44,937,938],{"class":84}," blank.any() ",[44,940,745],{"class":80},[44,942,943],{"class":84}," [])\n",[44,945,947,950,952,955,957,960,962,965],{"class":46,"line":946},21,[44,948,949],{"class":80},"    return",[44,951,385],{"class":84},[44,953,954],{"class":54},"\"Completeness\"",[44,956,793],{"class":84},[44,958,959],{"class":54},"\"Validity\"",[44,961,798],{"class":84},[44,963,964],{"class":54},"\"Uniqueness\"",[44,966,967],{"class":84},": uniqueness,\n",[44,969,971,974,976,979,982,985,988,991,993,996,999,1002],{"class":46,"line":970},22,[44,972,973],{"class":54},"            \"Score\"",[44,975,126],{"class":84},[44,977,978],{"class":104},"round",[44,980,981],{"class":84},"(score, ",[44,983,984],{"class":104},"1",[44,986,987],{"class":84},"), ",[44,989,990],{"class":54},"\"Examples\"",[44,992,126],{"class":84},[44,994,995],{"class":54},"\", \"",[44,997,998],{"class":84},".join(failing[:",[44,1000,1001],{"class":104},"4",[44,1003,1004],{"class":84},"])}\n",[44,1006,1008],{"class":46,"line":1007},23,[44,1009,98],{"emptyLinePlaceholder":97},[44,1011,1013,1015,1018],{"class":46,"line":1012},24,[44,1014,456],{"class":80},[44,1016,1017],{"class":459}," scorecard",[44,1019,1020],{"class":84},"(path):\n",[44,1022,1024,1027,1029],{"class":46,"line":1023},25,[44,1025,1026],{"class":84},"    df ",[44,1028,471],{"class":80},[44,1030,1031],{"class":84}," pd.read_excel(path)\n",[44,1033,1035,1038,1040,1043,1045,1048,1050,1053],{"class":46,"line":1034},26,[44,1036,1037],{"class":84},"    rows ",[44,1039,471],{"class":80},[44,1041,1042],{"class":84}," {col: score_column(df[col], rule) ",[44,1044,822],{"class":80},[44,1046,1047],{"class":84}," col, rule ",[44,1049,828],{"class":80},[44,1051,1052],{"class":104}," RULES",[44,1054,1055],{"class":84},".items()}\n",[44,1057,1059,1062,1064],{"class":46,"line":1058},27,[44,1060,1061],{"class":84},"    card ",[44,1063,471],{"class":80},[44,1065,1066],{"class":84}," pd.DataFrame(rows).T\n",[44,1068,1070,1072,1075,1077,1080,1083,1086,1089,1092,1094],{"class":46,"line":1069},28,[44,1071,949],{"class":80},[44,1073,1074],{"class":84}," card, ",[44,1076,978],{"class":104},[44,1078,1079],{"class":84},"(card[",[44,1081,1082],{"class":54},"\"Score\"",[44,1084,1085],{"class":84},"].astype(",[44,1087,1088],{"class":104},"float",[44,1090,1091],{"class":84},").mean(), ",[44,1093,984],{"class":104},[44,1095,431],{"class":84},[44,1097,1099],{"class":46,"line":1098},29,[44,1100,98],{"emptyLinePlaceholder":97},[44,1102,1104,1107,1109,1112,1115],{"class":46,"line":1103},30,[44,1105,1106],{"class":84},"card, overall ",[44,1108,471],{"class":80},[44,1110,1111],{"class":84}," scorecard(",[44,1113,1114],{"class":54},"\"customers_2026-09.xlsx\"",[44,1116,431],{"class":84},[44,1118,1120,1123,1126,1129,1131,1133,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156,1158,1161,1163,1166,1169,1171,1174],{"class":46,"line":1119},31,[44,1121,1122],{"class":104},"print",[44,1124,1125],{"class":84},"(card.drop(",[44,1127,1128],{"class":556},"columns",[44,1130,471],{"class":80},[44,1132,990],{"class":54},[44,1134,1135],{"class":84},").astype({",[44,1137,954],{"class":54},[44,1139,126],{"class":84},[44,1141,1088],{"class":104},[44,1143,132],{"class":84},[44,1145,959],{"class":54},[44,1147,126],{"class":84},[44,1149,1088],{"class":104},[44,1151,132],{"class":84},[44,1153,964],{"class":54},[44,1155,126],{"class":84},[44,1157,1088],{"class":104},[44,1159,1160],{"class":84},"}).round(",[44,1162,919],{"class":104},[44,1164,1165],{"class":84},").to_string(",[44,1167,1168],{"class":556},"na_rep",[44,1170,471],{"class":80},[44,1172,1173],{"class":54},"\"—\"",[44,1175,1176],{"class":84},"))\n",[44,1178,1180,1182,1185,1188],{"class":46,"line":1179},32,[44,1181,1122],{"class":104},[44,1183,1184],{"class":84},"(",[44,1186,1187],{"class":54},"\"overall score:\"",[44,1189,1190],{"class":84},", overall)\n",[10,1192,1193,1194,1197,1198,1201,1202,1204,1205,1208,1209,1212,1213,1216,1217,1220],{},"Each column gets three numbers between 0 and 1. ",[14,1195,1196],{},"Completeness"," is the share of values that are present, counting empty strings and placeholders like ",[14,1199,1200],{},"n\u002Fa"," as missing, because a cell containing ",[14,1203,1200],{}," is no more useful than an empty one. ",[14,1206,1207],{},"Validity"," is the share of present values that pass every rule for the column — pattern, allowed list, range and age. ",[14,1210,1211],{},"Uniqueness"," is the share of values that are not repeats, measured only for columns that should be unique, such as the customer ID. The weighted average of the dimensions that apply, times 100, is the column's score — a column with no uniqueness rule is scored on completeness and validity alone, rather than being handed uniqueness points for free, which would push every column towards a flattering score — and the file's score is the average over its columns. Up to three failing values are kept as examples, which is what turns a low score into something actionable: ",[14,1214,1215],{},"UK"," and ",[14,1218,1219],{},"Germany"," in the country column point straight at the fix.",[1222,1223,1231,1232,1231,1236,1231,1240,1231,1247,1231,1257,1231,1263,1231,1268,1231,1272,1231,1277,1231,1282,1231,1286,1231,1290,1231,1294,1231,1297,1231,1302,1231,1306,1231,1310,1231,1314,1231,1317,1231,1322,1231,1327],"svg",{"viewBox":1224,"role":1225,"ariaLabelledBy":1226,"xmlns":1229,"style":1230},"84 0 592 376","img",[1227,1228],"dq-dimensions-t","dq-dimensions-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[1233,1234,1235],"title",{"id":1227},"The dimensions behind a column score",[1237,1238,1239],"desc",{"id":1228},"Completeness counts present values, with blanks and placeholders such as n\u002Fa treated as missing. Validity checks present values against the column's pattern, allowed list, range or maximum age. Uniqueness checks key columns for repeats. The weighted sum gives the column score out of 100.",[1241,1242],"rect",{"x":1243,"y":332,"width":1244,"height":1245,"fill":1246},"84","592","376","#ffffff",[1241,1248],{"x":1249,"y":1250,"width":1251,"height":1252,"rx":1253,"fill":1254,"stroke":1255,"style":1256},"100.0","16.0","560.0","59.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[1258,1259,1196],"text",{"x":1260,"y":1261,"style":1262},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[1258,1264,1267],{"x":1260,"y":1265,"style":1266},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","present, not blank or n\u002Fa — weight 0.3",[46,1269],{"x1":1260,"y1":1270,"x2":1260,"y2":1271,"stroke":1255,"style":1256},"80.0","102.0",[1273,1274],"polygon",{"points":1275,"fill":1276},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[1241,1278],{"x":1249,"y":1279,"width":1251,"height":1252,"rx":1253,"fill":1280,"stroke":1281,"style":1256},"111.0","#fdefd8","var(--gold,#b4740a)",[1258,1283,1207],{"x":1260,"y":1284,"style":1285},"136.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle",[1258,1287,1289],{"x":1260,"y":1288,"style":1266},"156.0","pattern, allowed list, range, age — 0.4",[46,1291],{"x1":1260,"y1":1292,"x2":1260,"y2":1293,"stroke":1255,"style":1256},"175.0","197.0",[1273,1295],{"points":1296,"fill":1276},"380.0,201.0 375.0,192.0 385.0,192.0",[1241,1298],{"x":1249,"y":1299,"width":1251,"height":1252,"rx":1253,"fill":1300,"stroke":1301,"style":1256},"206.0","#d9f4f1","var(--teal,#0f9488)",[1258,1303,1211],{"x":1260,"y":1304,"style":1305},"231.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle",[1258,1307,1309],{"x":1260,"y":1308,"style":1266},"251.0","key columns only — 0.3",[46,1311],{"x1":1260,"y1":1312,"x2":1260,"y2":1313,"stroke":1255,"style":1256},"270.0","292.0",[1273,1315],{"points":1316,"fill":1276},"380.0,296.0 375.0,287.0 385.0,287.0",[1241,1318],{"x":1249,"y":1319,"width":1251,"height":1252,"rx":1253,"fill":1320,"stroke":1321,"style":1256},"301.0","#e7ebef","var(--line,#cdd5e6)",[1258,1323,1326],{"x":1260,"y":1324,"style":1325},"326.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Column score",[1258,1328,1330],{"x":1260,"y":1329,"style":1266},"346.0","0–100; file = average",[29,1332,1334],{"id":1333},"step-3-compare-with-last-month","Step 3 — Compare with last month",[34,1336,1338],{"className":71,"code":1337,"language":73,"meta":39,"style":39},"prev_card, prev_overall = scorecard(\"customers_2026-08.xlsx\")\ncompare = pd.DataFrame({\"Aug 2026\": prev_card[\"Score\"].astype(float), \"Sep 2026\": card[\"Score\"].astype(float)})\ncompare[\"Change\"] = compare[\"Sep 2026\"] - compare[\"Aug 2026\"]\ncompare.loc[\"Overall\"] = [prev_overall, overall, round(overall - prev_overall, 1)]\nprint(compare.to_string())\n",[41,1339,1340,1354,1393,1421,1451],{"__ignoreMap":39},[44,1341,1342,1345,1347,1349,1352],{"class":46,"line":47},[44,1343,1344],{"class":84},"prev_card, prev_overall ",[44,1346,471],{"class":80},[44,1348,1111],{"class":84},[44,1350,1351],{"class":54},"\"customers_2026-08.xlsx\"",[44,1353,431],{"class":84},[44,1355,1356,1359,1361,1364,1367,1370,1372,1374,1376,1378,1381,1384,1386,1388,1390],{"class":46,"line":94},[44,1357,1358],{"class":84},"compare ",[44,1360,471],{"class":80},[44,1362,1363],{"class":84}," pd.DataFrame({",[44,1365,1366],{"class":54},"\"Aug 2026\"",[44,1368,1369],{"class":84},": prev_card[",[44,1371,1082],{"class":54},[44,1373,1085],{"class":84},[44,1375,1088],{"class":104},[44,1377,987],{"class":84},[44,1379,1380],{"class":54},"\"Sep 2026\"",[44,1382,1383],{"class":84},": card[",[44,1385,1082],{"class":54},[44,1387,1085],{"class":84},[44,1389,1088],{"class":104},[44,1391,1392],{"class":84},")})\n",[44,1394,1395,1398,1401,1403,1405,1408,1410,1412,1415,1417,1419],{"class":46,"line":101},[44,1396,1397],{"class":84},"compare[",[44,1399,1400],{"class":54},"\"Change\"",[44,1402,933],{"class":84},[44,1404,471],{"class":80},[44,1406,1407],{"class":84}," compare[",[44,1409,1380],{"class":54},[44,1411,933],{"class":84},[44,1413,1414],{"class":80},"-",[44,1416,1407],{"class":84},[44,1418,1366],{"class":54},[44,1420,646],{"class":84},[44,1422,1423,1426,1429,1431,1433,1436,1438,1441,1443,1446,1448],{"class":46,"line":114},[44,1424,1425],{"class":84},"compare.loc[",[44,1427,1428],{"class":54},"\"Overall\"",[44,1430,933],{"class":84},[44,1432,471],{"class":80},[44,1434,1435],{"class":84}," [prev_overall, overall, ",[44,1437,978],{"class":104},[44,1439,1440],{"class":84},"(overall ",[44,1442,1414],{"class":80},[44,1444,1445],{"class":84}," prev_overall, ",[44,1447,984],{"class":104},[44,1449,1450],{"class":84},")]\n",[44,1452,1453,1455],{"class":46,"line":175},[44,1454,1122],{"class":104},[44,1456,1457],{"class":84},"(compare.to_string())\n",[10,1459,1460],{},"A score on its own is hard to judge; a score next to last month's is not. Running the same rules over the previous file and putting the two side by side shows whether quality is improving or slipping, and in which columns. September's extract is worse than August's in every column — more missing names, more malformed emails, more country names instead of codes — and the change column says by how much: the overall score drops from 98.8 to 97.4, with email down three points to 95.3 and country nearly two points to 95.8.",[1222,1462,1231,1468,1231,1471,1231,1474,1231,1479,1231,1485,1231,1489,1231,1495,1231,1499,1231,1503,1231,1507,1231,1510,1231,1514,1231,1518,1231,1522,1231,1526,1231,1530,1231,1533,1231,1536,1231,1540,1231,1542,1231,1545,1231,1547,1231,1550,1231,1552,1231,1555,1231,1557,1231,1560,1231,1563,1231,1567,1231,1569,1231,1573,1231,1575,1231,1577,1231,1579,1231,1582,1231,1584,1231,1587,1231,1589,1231,1592,1231,1595,1231,1598,1231,1600,1231,1603,1231,1605,1231,1608,1231,1610,1231,1613,1231,1615,1231,1618,1231,1620,1231,1623,1231,1626,1231,1629,1231,1631,1231,1634,1231,1636,1231,1639,1231,1641,1231,1644,1231,1648,1231,1652,1231,1654,1231,1657,1231,1660,1231,1664,1231,1666,1231,1669,1231,1671,1231,1674,1231,1676,1231,1679,1231,1681,1231,1685,1231,1687,1231,1690,1231,1693,1231,1697,1231,1699,1231,1702,1231,1704,1231,1707,1231,1709,1231,1712,1231,1714,1231,1717,1231,1719,1231,1722,1231,1725,1231,1729,1231,1731,1231,1734,1231,1736,1231,1739,1231,1741,1231,1744,1231,1746,1231,1749,1231,1751,1231,1754,1231,1757,1231,1761,1231,1763,1231,1766,1231,1768,1231,1771,1231,1773,1231,1776,1231,1778,1231,1781],{"viewBox":1463,"role":1225,"ariaLabelledBy":1464,"xmlns":1229,"style":1467},"102 0 555 280",[1465,1466],"dq-scores-t","dq-scores-d","width:100%;max-width:555px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[1233,1469,1470],{"id":1465},"Column scores, August against September",[1237,1472,1473],{"id":1466},"Every column scores lower in September. Email falls from 98.3 to 95.3 and Country from 97.7 to 95.8. The overall score falls from 98.8 to 97.4. Customer ID is nearly perfect apart from one duplicated ID.",[1241,1475],{"x":1476,"y":332,"width":1477,"height":1478,"fill":1246},"102","555","280",[1241,1480],{"x":1481,"y":1250,"width":1482,"height":1483,"rx":332,"fill":1320,"stroke":1321,"style":1484},"119.0","32.0","24.0","stroke-width:1px",[1241,1486],{"x":1487,"y":1250,"width":1488,"height":1483,"rx":332,"fill":1320,"stroke":1321,"style":1484},"151.0","100.3",[1258,1490,1494],{"x":1491,"y":1492,"style":1493},"201.1","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[1241,1496],{"x":1497,"y":1250,"width":1498,"height":1483,"rx":332,"fill":1320,"stroke":1321,"style":1484},"251.3","84.8",[1258,1500,1502],{"x":1501,"y":1492,"style":1493},"293.7","B",[1241,1504],{"x":1505,"y":1250,"width":1506,"height":1483,"rx":332,"fill":1320,"stroke":1321,"style":1484},"336.0","83.2",[1258,1508,149],{"x":1509,"y":1492,"style":1493},"377.7",[1241,1511],{"x":1512,"y":1250,"width":1513,"height":1483,"rx":332,"fill":1320,"stroke":1321,"style":1484},"419.3","71.8",[1258,1515,1517],{"x":1516,"y":1492,"style":1493},"455.2","D",[1241,1519],{"x":1520,"y":1250,"width":1521,"height":1483,"rx":332,"fill":1320,"stroke":1321,"style":1484},"491.1","149.9",[1258,1523,1525],{"x":1524,"y":1492,"style":1493},"566.1","E",[1241,1527],{"x":1481,"y":1528,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"40.0","28.0",[1258,1531,984],{"x":1532,"y":1252,"style":1493},"135.0",[1241,1534],{"x":1487,"y":1528,"width":1488,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},"stroke-width:1.5px",[1258,1537,1539],{"x":1491,"y":1252,"style":1538},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Column",[1241,1541],{"x":1497,"y":1528,"width":1498,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1543,1544],{"x":1501,"y":1252,"style":1538},"Aug 2026",[1241,1546],{"x":1505,"y":1528,"width":1506,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1548,1549],{"x":1509,"y":1252,"style":1538},"Sep 2026",[1241,1551],{"x":1512,"y":1528,"width":1513,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1553,1554],{"x":1516,"y":1252,"style":1538},"Change",[1241,1556],{"x":1520,"y":1528,"width":1521,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1558,1559],{"x":1524,"y":1252,"style":1538},"Typical failure",[1241,1561],{"x":1481,"y":1562,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"68.0",[1258,1564,1566],{"x":1532,"y":1565,"style":1493},"87.0","2",[1241,1568],{"x":1487,"y":1562,"width":1488,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1570,1572],{"x":1491,"y":1565,"style":1571},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Customer ID",[1241,1574],{"x":1497,"y":1562,"width":1498,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1576,1249],{"x":1501,"y":1565,"style":1571},[1241,1578],{"x":1505,"y":1562,"width":1506,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1580,1581],{"x":1509,"y":1565,"style":1571},"99.9",[1241,1583],{"x":1512,"y":1562,"width":1513,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1585,1586],{"x":1516,"y":1565,"style":1571},"−0.1",[1241,1588],{"x":1520,"y":1562,"width":1521,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1590,1591],{"x":1524,"y":1565,"style":1571},"one duplicate ID",[1241,1593],{"x":1481,"y":1594,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"96.0",[1258,1596,919],{"x":1532,"y":1597,"style":1493},"115.0",[1241,1599],{"x":1487,"y":1594,"width":1488,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1601,1602],{"x":1491,"y":1597,"style":1571},"Name",[1241,1604],{"x":1497,"y":1594,"width":1498,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1606,1607],{"x":1501,"y":1597,"style":1571},"99.1",[1241,1609],{"x":1505,"y":1594,"width":1506,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1611,1612],{"x":1509,"y":1597,"style":1571},"98.1",[1241,1614],{"x":1512,"y":1594,"width":1513,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1616,1617],{"x":1516,"y":1597,"style":1571},"−1.0",[1241,1619],{"x":1520,"y":1594,"width":1521,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1621,1622],{"x":1524,"y":1597,"style":1571},"blank",[1241,1624],{"x":1481,"y":1625,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"124.0",[1258,1627,1001],{"x":1532,"y":1628,"style":1493},"143.0",[1241,1630],{"x":1487,"y":1625,"width":1488,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1632,1633],{"x":1491,"y":1628,"style":1571},"Email",[1241,1635],{"x":1497,"y":1625,"width":1498,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1637,1638],{"x":1501,"y":1628,"style":1571},"98.3",[1241,1640],{"x":1505,"y":1625,"width":1506,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1642,1643],{"x":1509,"y":1628,"style":1571},"95.3",[1241,1645],{"x":1512,"y":1625,"width":1513,"height":1529,"rx":332,"fill":1646,"stroke":1647,"style":1535},"#fce9e9","var(--danger,#dc2626)",[1258,1649,1651],{"x":1516,"y":1628,"style":1650},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","−3.0",[1241,1653],{"x":1520,"y":1625,"width":1521,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1655,1656],{"x":1524,"y":1628,"style":1571},"missing @, n\u002Fa",[1241,1658],{"x":1481,"y":1659,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"152.0",[1258,1661,1663],{"x":1532,"y":1662,"style":1493},"171.0","5",[1241,1665],{"x":1487,"y":1659,"width":1488,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1667,1668],{"x":1491,"y":1662,"style":1571},"Country",[1241,1670],{"x":1497,"y":1659,"width":1498,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1672,1673],{"x":1501,"y":1662,"style":1571},"97.7",[1241,1675],{"x":1505,"y":1659,"width":1506,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1677,1678],{"x":1509,"y":1662,"style":1571},"95.8",[1241,1680],{"x":1512,"y":1659,"width":1513,"height":1529,"rx":332,"fill":1280,"stroke":1281,"style":1535},[1258,1682,1684],{"x":1516,"y":1662,"style":1683},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","−1.9",[1241,1686],{"x":1520,"y":1659,"width":1521,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1688,1689],{"x":1524,"y":1662,"style":1571},"UK, Germany",[1241,1691],{"x":1481,"y":1692,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"180.0",[1258,1694,1696],{"x":1532,"y":1695,"style":1493},"199.0","6",[1241,1698],{"x":1487,"y":1692,"width":1488,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1700,1701],{"x":1491,"y":1695,"style":1571},"Credit limit",[1241,1703],{"x":1497,"y":1692,"width":1498,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1705,1706],{"x":1501,"y":1695,"style":1571},"98.9",[1241,1708],{"x":1505,"y":1692,"width":1506,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1710,1711],{"x":1509,"y":1695,"style":1571},"98.0",[1241,1713],{"x":1512,"y":1692,"width":1513,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1715,1716],{"x":1516,"y":1695,"style":1571},"−0.9",[1241,1718],{"x":1520,"y":1692,"width":1521,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1720,1721],{"x":1524,"y":1695,"style":1571},"0, −500, 9,999,999",[1241,1723],{"x":1481,"y":1724,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"208.0",[1258,1726,1728],{"x":1532,"y":1727,"style":1493},"227.0","7",[1241,1730],{"x":1487,"y":1724,"width":1488,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1732,1733],{"x":1491,"y":1727,"style":1571},"Last order",[1241,1735],{"x":1497,"y":1724,"width":1498,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1737,1738],{"x":1501,"y":1727,"style":1571},"98.6",[1241,1740],{"x":1505,"y":1724,"width":1506,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1742,1743],{"x":1509,"y":1727,"style":1571},"97.3",[1241,1745],{"x":1512,"y":1724,"width":1513,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1747,1748],{"x":1516,"y":1727,"style":1571},"−1.3",[1241,1750],{"x":1520,"y":1724,"width":1521,"height":1529,"rx":332,"fill":1246,"stroke":1321,"style":1484},[1258,1752,1753],{"x":1524,"y":1727,"style":1571},"older than two years",[1241,1755],{"x":1481,"y":1756,"width":1482,"height":1529,"rx":332,"fill":1320,"stroke":1321,"style":1484},"236.0",[1258,1758,1760],{"x":1532,"y":1759,"style":1493},"255.0","8",[1241,1762],{"x":1487,"y":1756,"width":1488,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1764,1765],{"x":1491,"y":1759,"style":1538},"Overall",[1241,1767],{"x":1497,"y":1756,"width":1498,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1769,1770],{"x":1501,"y":1759,"style":1538},"98.8",[1241,1772],{"x":1505,"y":1756,"width":1506,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1774,1775],{"x":1509,"y":1759,"style":1538},"97.4",[1241,1777],{"x":1512,"y":1756,"width":1513,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[1258,1779,1780],{"x":1516,"y":1759,"style":1538},"−1.4",[1241,1782],{"x":1520,"y":1756,"width":1521,"height":1529,"rx":332,"fill":1254,"stroke":1255,"style":1535},[10,1784,1785,1786,1788,1789,1791,1792,1216,1795,1798,1799,1216,1801,1803],{},"Scores in the high nineties can look reassuring, so it helps to translate them back into rows. Behind the country column's 95.8 are 31 customers out of 400 whose country cannot be used for tax or shipping: 13 marked ",[14,1787,1215],{}," and 11 ",[14,1790,1219],{}," instead of the codes ",[14,1793,1794],{},"GB",[14,1796,1797],{},"DE",", and 7 with no country at all. Those are 31 records someone will fix by hand later if they are not fixed at source now — and the ",[14,1800,1215],{},[14,1802,1219],{}," entries suggest the fix is a single change in the system that produces the extract. That is exactly the evidence needed for a conversation with whoever produces the file, and it is far more persuasive than a list of individual errors.",[29,1805,1807],{"id":1806},"step-4-the-scorecard-workbook","Step 4 — The scorecard workbook",[34,1809,1811],{"className":71,"code":1810,"language":73,"meta":39,"style":39},"from openpyxl import load_workbook\nfrom openpyxl.formatting.rule import ColorScaleRule\n\npath = \"data_quality_customers_2026-09.xlsx\"\nout = card.copy()\nfor c in [\"Completeness\", \"Validity\", \"Uniqueness\"]:\n    out[c] = out[c].astype(float)\nwith pd.ExcelWriter(path) as xw:\n    out.reset_index(names=\"Column\").to_excel(xw, sheet_name=\"Scorecard\", index=False)\n    compare.reset_index(names=\"Column\").to_excel(xw, sheet_name=\"Trend\", index=False)\nwb = load_workbook(path)\nws = wb[\"Scorecard\"]\nfor col in \"BCD\":\n    for (cell,) in ws.iter_rows(min_row=2, min_col=ord(col) - 64, max_col=ord(col) - 64):\n        cell.number_format = \"0.0%\"\nws.conditional_formatting.add(f\"E2:E{ws.max_row}\", ColorScaleRule(start_type=\"num\", start_value=90, start_color=\"F8696B\",\n                              mid_type=\"num\", mid_value=96, mid_color=\"FFEB84\", end_type=\"num\", end_value=100, end_color=\"63BE7B\"))\nws.column_dimensions[\"F\"].width = 45\nwb.save(path)\nprint(out[[\"Score\", \"Examples\"]].to_string())\n",[41,1812,1813,1826,1838,1842,1852,1862,1887,1901,1914,1948,1978,1988,2002,2017,2073,2083,2139,2199,2215,2220],{"__ignoreMap":39},[44,1814,1815,1818,1821,1823],{"class":46,"line":47},[44,1816,1817],{"class":80},"from",[44,1819,1820],{"class":84}," openpyxl ",[44,1822,81],{"class":80},[44,1824,1825],{"class":84}," load_workbook\n",[44,1827,1828,1830,1833,1835],{"class":46,"line":94},[44,1829,1817],{"class":80},[44,1831,1832],{"class":84}," openpyxl.formatting.rule ",[44,1834,81],{"class":80},[44,1836,1837],{"class":84}," ColorScaleRule\n",[44,1839,1840],{"class":46,"line":101},[44,1841,98],{"emptyLinePlaceholder":97},[44,1843,1844,1847,1849],{"class":46,"line":114},[44,1845,1846],{"class":84},"path ",[44,1848,471],{"class":80},[44,1850,1851],{"class":54}," \"data_quality_customers_2026-09.xlsx\"\n",[44,1853,1854,1857,1859],{"class":46,"line":175},[44,1855,1856],{"class":84},"out ",[44,1858,471],{"class":80},[44,1860,1861],{"class":84}," card.copy()\n",[44,1863,1864,1866,1869,1871,1874,1876,1878,1880,1882,1884],{"class":46,"line":192},[44,1865,822],{"class":80},[44,1867,1868],{"class":84}," c ",[44,1870,828],{"class":80},[44,1872,1873],{"class":84}," [",[44,1875,954],{"class":54},[44,1877,132],{"class":84},[44,1879,959],{"class":54},[44,1881,132],{"class":84},[44,1883,964],{"class":54},[44,1885,1886],{"class":84},"]:\n",[44,1888,1889,1892,1894,1897,1899],{"class":46,"line":266},[44,1890,1891],{"class":84},"    out[c] ",[44,1893,471],{"class":80},[44,1895,1896],{"class":84}," out[c].astype(",[44,1898,1088],{"class":104},[44,1900,431],{"class":84},[44,1902,1903,1906,1909,1911],{"class":46,"line":310},[44,1904,1905],{"class":80},"with",[44,1907,1908],{"class":84}," pd.ExcelWriter(path) ",[44,1910,88],{"class":80},[44,1912,1913],{"class":84}," xw:\n",[44,1915,1916,1919,1922,1924,1927,1930,1933,1935,1938,1940,1942,1944,1946],{"class":46,"line":343},[44,1917,1918],{"class":84},"    out.reset_index(",[44,1920,1921],{"class":556},"names",[44,1923,471],{"class":80},[44,1925,1926],{"class":54},"\"Column\"",[44,1928,1929],{"class":84},").to_excel(xw, ",[44,1931,1932],{"class":556},"sheet_name",[44,1934,471],{"class":80},[44,1936,1937],{"class":54},"\"Scorecard\"",[44,1939,132],{"class":84},[44,1941,557],{"class":556},[44,1943,471],{"class":80},[44,1945,356],{"class":104},[44,1947,431],{"class":84},[44,1949,1950,1953,1955,1957,1959,1961,1963,1965,1968,1970,1972,1974,1976],{"class":46,"line":371},[44,1951,1952],{"class":84},"    compare.reset_index(",[44,1954,1921],{"class":556},[44,1956,471],{"class":80},[44,1958,1926],{"class":54},[44,1960,1929],{"class":84},[44,1962,1932],{"class":556},[44,1964,471],{"class":80},[44,1966,1967],{"class":54},"\"Trend\"",[44,1969,132],{"class":84},[44,1971,557],{"class":556},[44,1973,471],{"class":80},[44,1975,356],{"class":104},[44,1977,431],{"class":84},[44,1979,1980,1983,1985],{"class":46,"line":377},[44,1981,1982],{"class":84},"wb ",[44,1984,471],{"class":80},[44,1986,1987],{"class":84}," load_workbook(path)\n",[44,1989,1990,1993,1995,1998,2000],{"class":46,"line":417},[44,1991,1992],{"class":84},"ws ",[44,1994,471],{"class":80},[44,1996,1997],{"class":84}," wb[",[44,1999,1937],{"class":54},[44,2001,646],{"class":84},[44,2003,2004,2006,2009,2011,2014],{"class":46,"line":679},[44,2005,822],{"class":80},[44,2007,2008],{"class":84}," col ",[44,2010,828],{"class":80},[44,2012,2013],{"class":54}," \"BCD\"",[44,2015,2016],{"class":84},":\n",[44,2018,2019,2022,2025,2027,2030,2033,2035,2037,2039,2042,2044,2047,2050,2052,2055,2057,2060,2062,2064,2066,2068,2070],{"class":46,"line":691},[44,2020,2021],{"class":80},"    for",[44,2023,2024],{"class":84}," (cell,) ",[44,2026,828],{"class":80},[44,2028,2029],{"class":84}," ws.iter_rows(",[44,2031,2032],{"class":556},"min_row",[44,2034,471],{"class":80},[44,2036,1566],{"class":104},[44,2038,132],{"class":84},[44,2040,2041],{"class":556},"min_col",[44,2043,471],{"class":80},[44,2045,2046],{"class":104},"ord",[44,2048,2049],{"class":84},"(col) ",[44,2051,1414],{"class":80},[44,2053,2054],{"class":104}," 64",[44,2056,132],{"class":84},[44,2058,2059],{"class":556},"max_col",[44,2061,471],{"class":80},[44,2063,2046],{"class":104},[44,2065,2049],{"class":84},[44,2067,1414],{"class":80},[44,2069,2054],{"class":104},[44,2071,2072],{"class":84},"):\n",[44,2074,2075,2078,2080],{"class":46,"line":726},[44,2076,2077],{"class":84},"        cell.number_format ",[44,2079,471],{"class":80},[44,2081,2082],{"class":54}," \"0.0%\"\n",[44,2084,2085,2088,2091,2094,2097,2100,2103,2105,2108,2111,2113,2116,2118,2121,2123,2126,2128,2131,2133,2136],{"class":46,"line":751},[44,2086,2087],{"class":84},"ws.conditional_formatting.add(",[44,2089,2090],{"class":80},"f",[44,2092,2093],{"class":54},"\"E2:E",[44,2095,2096],{"class":222},"{",[44,2098,2099],{"class":84},"ws.max_row",[44,2101,2102],{"class":222},"}",[44,2104,143],{"class":54},[44,2106,2107],{"class":84},", ColorScaleRule(",[44,2109,2110],{"class":556},"start_type",[44,2112,471],{"class":80},[44,2114,2115],{"class":54},"\"num\"",[44,2117,132],{"class":84},[44,2119,2120],{"class":556},"start_value",[44,2122,471],{"class":80},[44,2124,2125],{"class":104},"90",[44,2127,132],{"class":84},[44,2129,2130],{"class":556},"start_color",[44,2132,471],{"class":80},[44,2134,2135],{"class":54},"\"F8696B\"",[44,2137,2138],{"class":84},",\n",[44,2140,2141,2144,2146,2148,2150,2153,2155,2158,2160,2163,2165,2168,2170,2173,2175,2177,2179,2182,2184,2187,2189,2192,2194,2197],{"class":46,"line":781},[44,2142,2143],{"class":556},"                              mid_type",[44,2145,471],{"class":80},[44,2147,2115],{"class":54},[44,2149,132],{"class":84},[44,2151,2152],{"class":556},"mid_value",[44,2154,471],{"class":80},[44,2156,2157],{"class":104},"96",[44,2159,132],{"class":84},[44,2161,2162],{"class":556},"mid_color",[44,2164,471],{"class":80},[44,2166,2167],{"class":54},"\"FFEB84\"",[44,2169,132],{"class":84},[44,2171,2172],{"class":556},"end_type",[44,2174,471],{"class":80},[44,2176,2115],{"class":54},[44,2178,132],{"class":84},[44,2180,2181],{"class":556},"end_value",[44,2183,471],{"class":80},[44,2185,2186],{"class":104},"100",[44,2188,132],{"class":84},[44,2190,2191],{"class":556},"end_color",[44,2193,471],{"class":80},[44,2195,2196],{"class":54},"\"63BE7B\"",[44,2198,1176],{"class":84},[44,2200,2201,2204,2207,2210,2212],{"class":46,"line":806},[44,2202,2203],{"class":84},"ws.column_dimensions[",[44,2205,2206],{"class":54},"\"F\"",[44,2208,2209],{"class":84},"].width ",[44,2211,471],{"class":80},[44,2213,2214],{"class":104}," 45\n",[44,2216,2217],{"class":46,"line":855},[44,2218,2219],{"class":84},"wb.save(path)\n",[44,2221,2222,2224,2227,2229,2231,2233],{"class":46,"line":898},[44,2223,1122],{"class":104},[44,2225,2226],{"class":84},"(out[[",[44,2228,1082],{"class":54},[44,2230,132],{"class":84},[44,2232,990],{"class":54},[44,2234,2235],{"class":84},"]].to_string())\n",[10,2237,2238,2239,2243],{},"The scorecard sheet lists each column with its three measures as percentages, its score coloured on a fixed red–amber–green scale, and the example failing values; the trend sheet holds the comparison with last month. The colour scale is anchored to fixed scores — red at 90 and below, amber around 96, green at 100 — so the colours mean the same every month and a column cannot look green just because all the others are worse. The scorecard is typically emailed back to the file's owner alongside the detailed list of failing rows from ",[23,2240,2242],{"href":2241},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-excel-columns-before-import-with-pandas\u002F","Validate Excel Columns Before Import with pandas",", the scorecard for the overview, the row list for the fixing.",[1222,2245,1231,2251,1231,2254,1231,2257,1231,2261,1231,2267,1231,2273,1231,2278,1231,2282,1231,2286,1231,2290,1231,2294,1231,2297,1231,2302,1231,2306,1231,2309,1231,2312,1231,2315],{"viewBox":2246,"role":1225,"ariaLabelledBy":2247,"xmlns":1229,"style":2250},"4 0 752 199",[2248,2249],"dq-card-vs-list-t","dq-card-vs-list-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[1233,2252,2253],{"id":2248},"Scorecard or error list",[1237,2255,2256],{"id":2249},"A scorecard summarises quality per column with scores and a trend, suits management and the file's owner, and shows direction over time. An error list gives every failing row and value, suits the person fixing the data, and says exactly what to change.",[1241,2258],{"x":1001,"y":332,"width":2259,"height":2260,"fill":1246},"752","199",[1241,2262],{"x":2263,"y":1250,"width":2264,"height":2265,"rx":2266,"fill":1254,"stroke":1255,"style":1256},"20.0","351.0","167.0","14",[1258,2268,2272],{"x":2269,"y":2270,"style":2271},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Scorecard",[46,2274],{"x1":2275,"y1":2276,"x2":2277,"y2":2276,"stroke":1255,"style":1484},"36.0","57.0","355.0",[1258,2279,2281],{"x":2269,"y":2280,"style":1571},"77.0","one score per column",[1258,2283,2285],{"x":2269,"y":2284,"style":1571},"103.0","trend against last month",[1258,2287,2289],{"x":2269,"y":2288,"style":1571},"129.0","for owners and managers",[1258,2291,2293],{"x":2269,"y":2292,"style":1571},"155.0","examples, not every row",[1241,2295],{"x":2296,"y":1250,"width":2264,"height":2265,"rx":2266,"fill":1300,"stroke":1301,"style":1256},"389.0",[1258,2298,2301],{"x":2299,"y":2270,"style":2300},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Error list",[46,2303],{"x1":2304,"y1":2276,"x2":2305,"y2":2276,"stroke":1301,"style":1484},"405.0","724.0",[1258,2307,2308],{"x":2299,"y":2280,"style":1571},"every failing row",[1258,2310,2311],{"x":2299,"y":2284,"style":1571},"exact values to fix",[1258,2313,2314],{"x":2299,"y":2288,"style":1571},"for whoever corrects the file",[1258,2316,2317],{"x":2299,"y":2292,"style":1571},"no sense of trend",[29,2319,2321],{"id":2320},"common-pitfalls","Common pitfalls",[2323,2324,2325,2338,2344,2350,2356],"ul",{},[2326,2327,2328,2332,2333,132,2335,2337],"li",{},[2329,2330,2331],"strong",{},"Placeholders counted as present."," Treat ",[14,2334,1200],{},[14,2336,1414],{}," and empty strings as missing.",[2326,2339,2340,2343],{},[2329,2341,2342],{},"Rules scattered through code."," Declare them per column in one table.",[2326,2345,2346,2349],{},[2329,2347,2348],{},"Uniqueness on every column."," Measure it only where values must be unique.",[2326,2351,2352,2355],{},[2329,2353,2354],{},"Scores without examples."," Show a few failing values so the fix is obvious.",[2326,2357,2358,2361],{},[2329,2359,2360],{},"No baseline."," Score last month's file with the same rules for comparison.",[29,2363,2365],{"id":2364},"conclusion","Conclusion",[10,2367,2368],{},"A data quality scorecard measures every column of a file for completeness, validity and, where it applies, uniqueness, using rules declared once per column — required, pattern, allowed values, range, maximum age. Weighted into a score per column and averaged for the file, with examples of failing values and a comparison against the previous file under the same rules, it gives the owner of the data a clear, trackable measure of how good it is and where to improve.",[29,2370,2372],{"id":2371},"frequently-asked-questions","Frequently asked questions",[10,2374,2375,2378],{},[2329,2376,2377],{},"How do I measure data completeness in pandas?","\nTreat NaN, empty strings and placeholders such as \"n\u002Fa\" as missing and take one minus the share of missing values.",[10,2380,2381,2384,2385,2388],{},[2329,2382,2383],{},"How do I check values against a pattern in pandas?","\nUse ",[41,2386,2387],{},"Series.str.match(pattern)"," on the non-blank values and take the share that match.",[10,2390,2391,2394],{},[2329,2392,2393],{},"What dimensions should a data quality scorecard include?","\nCommonly completeness, validity, uniqueness, consistency with allowed values and timeliness.",[10,2396,2397,2400],{},[2329,2398,2399],{},"Why compare the score with the previous file?","\nA trend shows whether quality is improving and makes the conversation with the data's owner concrete.",[29,2402,2404],{"id":2403},"related","Related",[2323,2406,2407,2413,2418,2425],{},[2326,2408,2409,2410,2412],{},"Up one level: ",[23,2411,26],{"href":25}," — checking data quality.",[2326,2414,2415,2417],{},[23,2416,2242],{"href":2241}," — the row-level checks.",[2326,2419,2420,2424],{},[23,2421,2423],{"href":2422},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fdetect-schema-drift-between-monthly-excel-files\u002F","Detect Schema Drift Between Monthly Excel Files"," — when the columns change.",[2326,2426,2427,2431],{},[23,2428,2430],{"href":2429},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fvalidate-dates-fall-within-an-expected-period\u002F","Validate Dates Fall Within an Expected Period"," — one dimension in depth.",[2433,2434,2435],"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 .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}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_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}",{"title":39,"searchDepth":94,"depth":94,"links":2437},[2438,2439,2440,2441,2442,2443,2444,2445,2446],{"id":31,"depth":94,"text":32},{"id":67,"depth":94,"text":68},{"id":445,"depth":94,"text":446},{"id":1333,"depth":94,"text":1334},{"id":1806,"depth":94,"text":1807},{"id":2320,"depth":94,"text":2321},{"id":2364,"depth":94,"text":2365},{"id":2371,"depth":94,"text":2372},{"id":2403,"depth":94,"text":2404},"2026-10-02","Score an incoming Excel file on data quality with pandas, column by column: completeness, validity against a type, pattern or range, uniqueness for key columns, consistency with an allowed list and freshness of dates — declared once in a small rules table, combined into a score per column and for the file, compared with last month's file, and written as a colour-coded scorecard with sample failing values.","md",[2451,2453,2455,2457],{"q":2377,"a":2452},"Treat NaN, empty strings and placeholders such as \"n\u002Fa\" as missing and take one minus the share of missing values.",{"q":2383,"a":2454},"Use Series.str.match(pattern) on the non-blank values and take the share that match.",{"q":2393,"a":2456},"Commonly completeness, validity, uniqueness, consistency with allowed values and timeliness.",{"q":2399,"a":2458},"A trend shows whether quality is improving and makes the conversation with the data's owner concrete.","data quality scorecard excel python",null,{"breadcrumb":2462},[2463,2465,2468],{"name":2464,"item":885},"Home",{"name":2466,"item":2467},"Advanced Data Transformation and Cleaning for Excel Automation","\u002Fadvanced-data-transformation-and-cleaning\u002F",{"name":26,"item":25},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file",{"title":2471,"description":2472},"Build a Data Quality Scorecard for an Excel File in Python","Data quality scorecard with pandas: completeness, validity, uniqueness, allowed values and freshness per column from a rules table, overall score, comparison with last month and an Excel scorecard.","build-a-data-quality-scorecard-for-an-excel-file","advanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fbuild-a-data-quality-scorecard-for-an-excel-file\u002Findex",[2476,2477],"data-validation","report-layouts","how-to","AiK6msQm3y-mYmhttJIkrh5lshwNVWsNtQS_qkNlfbU",[2481,2485],{"title":2482,"path":2483,"stem":2484,"children":-1},"Anonymise Personal Data in an Excel File with Python","\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fanonymise-personal-data-in-an-excel-file-with-python","advanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fanonymise-personal-data-in-an-excel-file-with-python\u002Findex",{"title":2486,"path":2487,"stem":2488,"children":-1},"Check Excel Data Types with pandas","\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-excel-data-types-with-pandas","advanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python\u002Fcheck-excel-data-types-with-pandas\u002Findex",[2490,2492],{"path":2491,"title":2466},"\u002Fadvanced-data-transformation-and-cleaning",{"path":2493,"title":26},"\u002Fadvanced-data-transformation-and-cleaning\u002Fvalidating-excel-data-with-python",[],[2496,2499,2502,2505,2508,2511],{"path":2497,"title":2498,"kind":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fdetect-outliers-in-excel-data-with-pandas","Detect Outliers in Excel Data with pandas",{"path":2500,"title":2501,"kind":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fapplying-conditional-formatting-with-openpyxl\u002Fhighlight-cells-containing-text-with-openpyxl","Highlight Cells Containing Specific Text with openpyxl",{"path":2503,"title":2504,"kind":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Ffind-near-duplicate-names-within-one-excel-column","Find Near-Duplicate Names Within One Excel Column",{"path":2506,"title":2507,"kind":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fextracting-data-from-messy-excel-sheets\u002Fread-dropdown-lists-from-an-existing-workbook","Read Dropdown Lists and Validation Rules from an Existing Workbook",{"path":2509,"title":2510,"kind":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Ffind-and-report-missing-values-in-an-excel-file","Find and Report Missing Values in an Excel File",{"path":2512,"title":2513,"kind":2478},"\u002Fadvanced-data-transformation-and-cleaning\u002Fhandling-missing-data-in-excel-reports\u002Fflag-rows-with-too-many-missing-values","Flag Rows with Too Many Missing Values",1790972098463]