[{"data":1,"prerenderedAt":1791},["ShallowReactive",2],{"doc:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names":3,"surround:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names":1755,"ancestors:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names":1764,"children:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names":1769,"related:\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names":1770},{"id":4,"title":5,"body":6,"dateModified":1721,"datePublished":1721,"description":1722,"extension":1723,"faq":1724,"keyword":1733,"kind":1734,"meta":1735,"navigation":121,"path":1744,"seo":1745,"slug":1748,"stem":1749,"topics":1750,"type":1753,"__hash__":1754},"docs\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names\u002Findex.md","Fix KeyError from Spaces in Excel Column Names",{"type":7,"value":8,"toc":1708},"minimark",[9,33,38,66,89,93,266,295,299,555,582,777,781,1021,1039,1169,1240,1244,1502,1535,1539,1550,1554,1565,1569,1613,1617,1631,1635,1641,1655,1665,1671,1675,1704],[10,11,12,13,17,18,22,23,26,27,32],"p",{},"The column is right there in the spreadsheet — ",[14,15,16],"em",{},"Net Amount"," — and the code asks for ",[19,20,21],"code",{},"df[\"Net Amount\"]",", and pandas raises ",[19,24,25],{},"KeyError: 'Net Amount'",". Nothing is misspelled that you can see. The cause is almost always a character you cannot see: a trailing space after the heading, a non-breaking space between the words copied in from a web page or an ERP export, a line break someone typed with Alt+Enter to wrap the heading, or a zero-width space that came along with a paste. To the eye the headings are identical; to Python they are different strings. This page reproduces the error, shows the hidden characters, suggests the column that was meant, and fixes the headings once, right after reading. It belongs to ",[28,29,31],"a",{"href":30},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002F","Troubleshooting Common Python Excel Errors",".",[34,35,37],"h2",{"id":36},"prerequisites","Prerequisites",[39,40,45],"pre",{"className":41,"code":42,"language":43,"meta":44,"style":44},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[19,46,47],{"__ignoreMap":44},[48,49,52,56,60,63],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sMTad","pip",[48,57,59],{"class":58},"srMev"," install",[48,61,62],{"class":58}," pandas",[48,64,65],{"class":58}," openpyxl\n",[10,67,68,69,72,73,72,76,78,79,72,82,85,86,32],{},"The sample workbook has six headings that look ordinary in Excel: ",[14,70,71],{},"Invoice",", ",[14,74,75],{},"Customer Name",[14,77,16],{}," (wrapped onto two lines), ",[14,80,81],{},"VAT",[14,83,84],{},"Due–Date"," and ",[14,87,88],{},"Region",[34,90,92],{"id":91},"step-1-reproduce-the-error-and-look-at-the-real-names","Step 1 — Reproduce the error and look at the real names",[39,94,98],{"className":95,"code":96,"language":97,"meta":44,"style":44},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"invoices.xlsx\")\nfor wanted in [\"Invoice\", \"Customer Name\", \"Net Amount\", \"VAT\", \"Due-Date\"]:\n    try:\n        df[wanted]\n    except KeyError as e:\n        print(\"KeyError:\", e)\nprint(list(df.columns))\nprint([repr(c) for c in df.columns])\n","python",[19,99,100,116,123,141,182,191,197,213,228,242],{"__ignoreMap":44},[48,101,102,106,110,113],{"class":50,"line":51},[48,103,105],{"class":104},"s-kum","import",[48,107,109],{"class":108},"skGVy"," pandas ",[48,111,112],{"class":104},"as",[48,114,115],{"class":108}," pd\n",[48,117,119],{"class":50,"line":118},2,[48,120,122],{"emptyLinePlaceholder":121},true,"\n",[48,124,126,129,132,135,138],{"class":50,"line":125},3,[48,127,128],{"class":108},"df ",[48,130,131],{"class":104},"=",[48,133,134],{"class":108}," pd.read_excel(",[48,136,137],{"class":58},"\"invoices.xlsx\"",[48,139,140],{"class":108},")\n",[48,142,144,147,150,153,156,159,161,164,166,169,171,174,176,179],{"class":50,"line":143},4,[48,145,146],{"class":104},"for",[48,148,149],{"class":108}," wanted ",[48,151,152],{"class":104},"in",[48,154,155],{"class":108}," [",[48,157,158],{"class":58},"\"Invoice\"",[48,160,72],{"class":108},[48,162,163],{"class":58},"\"Customer Name\"",[48,165,72],{"class":108},[48,167,168],{"class":58},"\"Net Amount\"",[48,170,72],{"class":108},[48,172,173],{"class":58},"\"VAT\"",[48,175,72],{"class":108},[48,177,178],{"class":58},"\"Due-Date\"",[48,180,181],{"class":108},"]:\n",[48,183,185,188],{"class":50,"line":184},5,[48,186,187],{"class":104},"    try",[48,189,190],{"class":108},":\n",[48,192,194],{"class":50,"line":193},6,[48,195,196],{"class":108},"        df[wanted]\n",[48,198,200,203,207,210],{"class":50,"line":199},7,[48,201,202],{"class":104},"    except",[48,204,206],{"class":205},"sP0c6"," KeyError",[48,208,209],{"class":104}," as",[48,211,212],{"class":108}," e:\n",[48,214,216,219,222,225],{"class":50,"line":215},8,[48,217,218],{"class":205},"        print",[48,220,221],{"class":108},"(",[48,223,224],{"class":58},"\"KeyError:\"",[48,226,227],{"class":108},", e)\n",[48,229,231,234,236,239],{"class":50,"line":230},9,[48,232,233],{"class":205},"print",[48,235,221],{"class":108},[48,237,238],{"class":205},"list",[48,240,241],{"class":108},"(df.columns))\n",[48,243,245,247,250,253,256,258,261,263],{"class":50,"line":244},10,[48,246,233],{"class":205},[48,248,249],{"class":108},"([",[48,251,252],{"class":205},"repr",[48,254,255],{"class":108},"(c) ",[48,257,146],{"class":104},[48,259,260],{"class":108}," c ",[48,262,152],{"class":104},[48,264,265],{"class":108}," df.columns])\n",[10,267,268,269,272,273,276,277,280,281,284,285,288,289,291,292,294],{},"Five of the six lookups fail. Printing the list of columns does not help much — the trailing space and the non-breaking space look like ordinary spaces, and the zero-width space shows nothing at all. ",[19,270,271],{},"repr()"," is the first tool that tells the truth: it shows ",[19,274,275],{},"'Invoice '"," with its trailing space, ",[19,278,279],{},"'Customer\\xa0Name'"," with the non-breaking space as an escape, ",[19,282,283],{},"'Net\\nAmount'"," with the line break, and ",[19,286,287],{},"'​VAT'"," with the zero-width space in front. The dash in ",[14,290,84],{}," is an en dash, which ",[19,293,252],{}," shows as the character itself, so it still looks like a hyphen unless you know to look for it.",[34,296,298],{"id":297},"step-2-name-every-unusual-character","Step 2 — Name every unusual character",[39,300,302],{"className":95,"code":301,"language":97,"meta":44,"style":44},"import unicodedata\n\ndef reveal(name):\n    odd = [(i, ch, unicodedata.name(ch, f\"U+{ord(ch):04X}\")) for i, ch in enumerate(name)\n           if not (ch.isalnum() or ch == \" \") or (ch == \" \" and (i == 0 or i == len(name) - 1))]\n    return odd\n\nfor col in df.columns:\n    found = reveal(col)\n    if found:\n        print(f\"{col!r:22}\", \"; \".join(f\"pos {i}: {n}\" for i, _, n in found))\n",[19,303,304,311,315,327,378,447,455,459,471,481,489],{"__ignoreMap":44},[48,305,306,308],{"class":50,"line":51},[48,307,105],{"class":104},[48,309,310],{"class":108}," unicodedata\n",[48,312,313],{"class":50,"line":118},[48,314,122],{"emptyLinePlaceholder":121},[48,316,317,320,324],{"class":50,"line":125},[48,318,319],{"class":104},"def",[48,321,323],{"class":322},"s_Opv"," reveal",[48,325,326],{"class":108},"(name):\n",[48,328,329,332,334,337,340,343,347,350,353,356,359,362,365,367,370,372,375],{"class":50,"line":143},[48,330,331],{"class":108},"    odd ",[48,333,131],{"class":104},[48,335,336],{"class":108}," [(i, ch, unicodedata.name(ch, ",[48,338,339],{"class":104},"f",[48,341,342],{"class":58},"\"U+",[48,344,346],{"class":345},"sSjpA","{",[48,348,349],{"class":205},"ord",[48,351,352],{"class":108},"(ch)",[48,354,355],{"class":104},":04X",[48,357,358],{"class":345},"}",[48,360,361],{"class":58},"\"",[48,363,364],{"class":108},")) ",[48,366,146],{"class":104},[48,368,369],{"class":108}," i, ch ",[48,371,152],{"class":104},[48,373,374],{"class":205}," enumerate",[48,376,377],{"class":108},"(name)\n",[48,379,380,383,386,389,392,395,398,401,404,406,409,411,413,416,419,421,424,427,430,432,435,438,441,444],{"class":50,"line":184},[48,381,382],{"class":104},"           if",[48,384,385],{"class":104}," not",[48,387,388],{"class":108}," (ch.isalnum() ",[48,390,391],{"class":104},"or",[48,393,394],{"class":108}," ch ",[48,396,397],{"class":104},"==",[48,399,400],{"class":58}," \" \"",[48,402,403],{"class":108},") ",[48,405,391],{"class":104},[48,407,408],{"class":108}," (ch ",[48,410,397],{"class":104},[48,412,400],{"class":58},[48,414,415],{"class":104}," and",[48,417,418],{"class":108}," (i ",[48,420,397],{"class":104},[48,422,423],{"class":205}," 0",[48,425,426],{"class":104}," or",[48,428,429],{"class":108}," i ",[48,431,397],{"class":104},[48,433,434],{"class":205}," len",[48,436,437],{"class":108},"(name) ",[48,439,440],{"class":104},"-",[48,442,443],{"class":205}," 1",[48,445,446],{"class":108},"))]\n",[48,448,449,452],{"class":50,"line":193},[48,450,451],{"class":104},"    return",[48,453,454],{"class":108}," odd\n",[48,456,457],{"class":50,"line":199},[48,458,122],{"emptyLinePlaceholder":121},[48,460,461,463,466,468],{"class":50,"line":215},[48,462,146],{"class":104},[48,464,465],{"class":108}," col ",[48,467,152],{"class":104},[48,469,470],{"class":108}," df.columns:\n",[48,472,473,476,478],{"class":50,"line":230},[48,474,475],{"class":108},"    found ",[48,477,131],{"class":104},[48,479,480],{"class":108}," reveal(col)\n",[48,482,483,486],{"class":50,"line":244},[48,484,485],{"class":104},"    if",[48,487,488],{"class":108}," found:\n",[48,490,492,494,496,498,500,502,505,508,510,512,514,517,520,522,525,527,530,532,535,537,540,542,544,547,550,552],{"class":50,"line":491},11,[48,493,218],{"class":205},[48,495,221],{"class":108},[48,497,339],{"class":104},[48,499,361],{"class":58},[48,501,346],{"class":345},[48,503,504],{"class":108},"col",[48,506,507],{"class":104},"!r:22",[48,509,358],{"class":345},[48,511,361],{"class":58},[48,513,72],{"class":108},[48,515,516],{"class":58},"\"; \"",[48,518,519],{"class":108},".join(",[48,521,339],{"class":104},[48,523,524],{"class":58},"\"pos ",[48,526,346],{"class":345},[48,528,529],{"class":108},"i",[48,531,358],{"class":345},[48,533,534],{"class":58},": ",[48,536,346],{"class":345},[48,538,539],{"class":108},"n",[48,541,358],{"class":345},[48,543,361],{"class":58},[48,545,546],{"class":104}," for",[48,548,549],{"class":108}," i, _, n ",[48,551,152],{"class":104},[48,553,554],{"class":108}," found))\n",[10,556,557,560,561,72,564,72,567,570,571,574,575,577,578,581],{},[19,558,559],{},"unicodedata.name()"," turns each suspicious character into its official name, which removes all guesswork: ",[14,562,563],{},"NO-BREAK SPACE",[14,565,566],{},"ZERO WIDTH SPACE",[14,568,569],{},"EN DASH",", and an ordinary ",[14,572,573],{},"SPACE"," at the end of ",[14,576,71],{},". Control characters such as the line feed from Alt+Enter have no name in the Unicode database, so the function falls back to the code point, ",[14,579,580],{},"U+000A","; the common ones are worth recognising on sight — U+000A is a line feed, U+000D a carriage return, U+0009 a tab. The function flags anything that is not a letter, digit or interior space, plus spaces at either end. Running it over the columns of a misbehaving file takes a second and usually ends the investigation, and it is worth keeping in a utilities module for exactly that purpose.",[583,584,592,593,592,597,592,601,592,609,592,618,592,622,592,629,592,633,592,637,592,641,592,645,592,649,592,654,592,659,592,663,592,665,592,668,592,670,592,673,592,676,592,680,592,682,592,685,592,687,592,689,592,693,592,697,592,700,592,704,592,706,592,708,592,710,592,712,592,714,592,716,592,719,592,723,592,725,592,727,592,729,592,731,592,733,592,736,592,739,592,743,592,745,592,747,592,749,592,752,592,754,592,756,592,759,592,763,592,765,592,768,592,770,592,773,592,775],"svg",{"viewBox":585,"role":586,"ariaLabelledBy":587,"xmlns":590,"style":591},"128 0 503 224","img",[588,589],"ke-chars-t","ke-chars-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:503px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[594,595,596],"title",{"id":588},"The invisible differences",[598,599,600],"desc",{"id":589},"Invoice has a trailing space. Customer Name has a no-break space between the words. Net Amount has a line feed from Alt+Enter. VAT starts with a zero-width space. Due–Date uses an en dash instead of a hyphen.",[602,603],"rect",{"x":604,"y":605,"width":606,"height":607,"fill":608},"128","0","503","224","#ffffff",[602,610],{"x":611,"y":612,"width":613,"height":614,"rx":605,"fill":615,"stroke":616,"style":617},"144.6","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[602,619],{"x":620,"y":612,"width":621,"height":614,"rx":605,"fill":615,"stroke":616,"style":617},"176.6","123.6",[623,624,628],"text",{"x":625,"y":626,"style":627},"238.4","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[602,630],{"x":631,"y":612,"width":632,"height":614,"rx":605,"fill":615,"stroke":616,"style":617},"300.3","157.2",[623,634,636],{"x":635,"y":626,"style":627},"378.9","B",[602,638],{"x":639,"y":612,"width":640,"height":614,"rx":605,"fill":615,"stroke":616,"style":617},"457.5","157.9",[623,642,644],{"x":643,"y":626,"style":627},"536.4","C",[602,646],{"x":611,"y":647,"width":613,"height":648,"rx":605,"fill":615,"stroke":616,"style":617},"40.0","28.0",[623,650,653],{"x":651,"y":652,"style":627},"160.6","59.0","1",[602,655],{"x":620,"y":647,"width":621,"height":648,"rx":605,"fill":656,"stroke":657,"style":658},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[623,660,662],{"x":625,"y":652,"style":661},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Looks like",[602,664],{"x":631,"y":647,"width":632,"height":648,"rx":605,"fill":656,"stroke":657,"style":658},[623,666,667],{"x":635,"y":652,"style":661},"Actually is",[602,669],{"x":639,"y":647,"width":640,"height":648,"rx":605,"fill":656,"stroke":657,"style":658},[623,671,672],{"x":643,"y":652,"style":661},"Character",[602,674],{"x":611,"y":675,"width":613,"height":648,"rx":605,"fill":615,"stroke":616,"style":617},"68.0",[623,677,679],{"x":651,"y":678,"style":627},"87.0","2",[602,681],{"x":620,"y":675,"width":621,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,683,71],{"x":625,"y":678,"style":684},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle",[602,686],{"x":631,"y":675,"width":632,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,688,275],{"x":635,"y":678,"style":684},[602,690],{"x":639,"y":675,"width":640,"height":648,"rx":605,"fill":691,"stroke":692,"style":658},"#fce9e9","var(--danger,#dc2626)",[623,694,696],{"x":643,"y":678,"style":695},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","SPACE at the end",[602,698],{"x":611,"y":699,"width":613,"height":648,"rx":605,"fill":615,"stroke":616,"style":617},"96.0",[623,701,703],{"x":651,"y":702,"style":627},"115.0","3",[602,705],{"x":620,"y":699,"width":621,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,707,75],{"x":625,"y":702,"style":684},[602,709],{"x":631,"y":699,"width":632,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,711,279],{"x":635,"y":702,"style":684},[602,713],{"x":639,"y":699,"width":640,"height":648,"rx":605,"fill":691,"stroke":692,"style":658},[623,715,563],{"x":643,"y":702,"style":695},[602,717],{"x":611,"y":718,"width":613,"height":648,"rx":605,"fill":615,"stroke":616,"style":617},"124.0",[623,720,722],{"x":651,"y":721,"style":627},"143.0","4",[602,724],{"x":620,"y":718,"width":621,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,726,16],{"x":625,"y":721,"style":684},[602,728],{"x":631,"y":718,"width":632,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,730,283],{"x":635,"y":721,"style":684},[602,732],{"x":639,"y":718,"width":640,"height":648,"rx":605,"fill":691,"stroke":692,"style":658},[623,734,735],{"x":643,"y":721,"style":695},"LINE FEED (Alt+Enter)",[602,737],{"x":611,"y":738,"width":613,"height":648,"rx":605,"fill":615,"stroke":616,"style":617},"152.0",[623,740,742],{"x":651,"y":741,"style":627},"171.0","5",[602,744],{"x":620,"y":738,"width":621,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,746,81],{"x":625,"y":741,"style":684},[602,748],{"x":631,"y":738,"width":632,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,750,751],{"x":635,"y":741,"style":684},"'\\u200bVAT'",[602,753],{"x":639,"y":738,"width":640,"height":648,"rx":605,"fill":691,"stroke":692,"style":658},[623,755,566],{"x":643,"y":741,"style":695},[602,757],{"x":611,"y":758,"width":613,"height":648,"rx":605,"fill":615,"stroke":616,"style":617},"180.0",[623,760,762],{"x":651,"y":761,"style":627},"199.0","6",[602,764],{"x":620,"y":758,"width":621,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,766,767],{"x":625,"y":761,"style":684},"Due-Date",[602,769],{"x":631,"y":758,"width":632,"height":648,"rx":605,"fill":608,"stroke":616,"style":617},[623,771,772],{"x":635,"y":761,"style":684},"'Due–Date'",[602,774],{"x":639,"y":758,"width":640,"height":648,"rx":605,"fill":691,"stroke":692,"style":658},[623,776,569],{"x":643,"y":761,"style":695},[34,778,780],{"id":779},"step-3-normalise-the-headings-once-right-after-reading","Step 3 — Normalise the headings once, right after reading",[39,782,784],{"className":95,"code":783,"language":97,"meta":44,"style":44},"import re\n\ndef clean_header(name):\n    text = unicodedata.normalize(\"NFKC\", str(name))          # NBSP -> space, full-width -> normal, etc.\n    text = re.sub(r\"[​-‍⁠﻿]\", \"\", text)   # zero-width characters and BOM\n    text = re.sub(r\"[‐-―−]\", \"-\", text)        # hyphen and dash variants -> '-'\n    text = re.sub(r\"\\s+\", \" \", text)                          # line breaks, tabs, runs of spaces\n    return text.strip()\n\ndf = pd.read_excel(\"invoices.xlsx\").rename(columns=clean_header)\nprint([repr(c) for c in df.columns])\nprint(df[[\"Invoice\", \"Customer Name\", \"Net Amount\", \"VAT\", \"Due-Date\"]].to_string(index=False))\n",[19,785,786,793,797,806,831,867,899,930,937,941,963,981],{"__ignoreMap":44},[48,787,788,790],{"class":50,"line":51},[48,789,105],{"class":104},[48,791,792],{"class":108}," re\n",[48,794,795],{"class":50,"line":118},[48,796,122],{"emptyLinePlaceholder":121},[48,798,799,801,804],{"class":50,"line":125},[48,800,319],{"class":104},[48,802,803],{"class":322}," clean_header",[48,805,326],{"class":108},[48,807,808,811,813,816,819,821,824,827],{"class":50,"line":143},[48,809,810],{"class":108},"    text ",[48,812,131],{"class":104},[48,814,815],{"class":108}," unicodedata.normalize(",[48,817,818],{"class":58},"\"NFKC\"",[48,820,72],{"class":108},[48,822,823],{"class":205},"str",[48,825,826],{"class":108},"(name))          ",[48,828,830],{"class":829},"s-wDw","# NBSP -> space, full-width -> normal, etc.\n",[48,832,833,835,837,840,843,845,848,851,854,856,858,861,864],{"class":50,"line":184},[48,834,810],{"class":108},[48,836,131],{"class":104},[48,838,839],{"class":108}," re.sub(",[48,841,842],{"class":104},"r",[48,844,361],{"class":58},[48,846,847],{"class":205},"[",[48,849,850],{"class":345},"​-‍⁠﻿",[48,852,853],{"class":205},"]",[48,855,361],{"class":58},[48,857,72],{"class":108},[48,859,860],{"class":58},"\"\"",[48,862,863],{"class":108},", text)   ",[48,865,866],{"class":829},"# zero-width characters and BOM\n",[48,868,869,871,873,875,877,879,881,884,886,888,890,893,896],{"class":50,"line":193},[48,870,810],{"class":108},[48,872,131],{"class":104},[48,874,839],{"class":108},[48,876,842],{"class":104},[48,878,361],{"class":58},[48,880,847],{"class":205},[48,882,883],{"class":345},"‐-―−",[48,885,853],{"class":205},[48,887,361],{"class":58},[48,889,72],{"class":108},[48,891,892],{"class":58},"\"-\"",[48,894,895],{"class":108},", text)        ",[48,897,898],{"class":829},"# hyphen and dash variants -> '-'\n",[48,900,901,903,905,907,909,911,914,917,919,921,924,927],{"class":50,"line":199},[48,902,810],{"class":108},[48,904,131],{"class":104},[48,906,839],{"class":108},[48,908,842],{"class":104},[48,910,361],{"class":58},[48,912,913],{"class":205},"\\s",[48,915,916],{"class":104},"+",[48,918,361],{"class":58},[48,920,72],{"class":108},[48,922,923],{"class":58},"\" \"",[48,925,926],{"class":108},", text)                          ",[48,928,929],{"class":829},"# line breaks, tabs, runs of spaces\n",[48,931,932,934],{"class":50,"line":215},[48,933,451],{"class":104},[48,935,936],{"class":108}," text.strip()\n",[48,938,939],{"class":50,"line":230},[48,940,122],{"emptyLinePlaceholder":121},[48,942,943,945,947,949,951,954,958,960],{"class":50,"line":244},[48,944,128],{"class":108},[48,946,131],{"class":104},[48,948,134],{"class":108},[48,950,137],{"class":58},[48,952,953],{"class":108},").rename(",[48,955,957],{"class":956},"sa561","columns",[48,959,131],{"class":104},[48,961,962],{"class":108},"clean_header)\n",[48,964,965,967,969,971,973,975,977,979],{"class":50,"line":491},[48,966,233],{"class":205},[48,968,249],{"class":108},[48,970,252],{"class":205},[48,972,255],{"class":108},[48,974,146],{"class":104},[48,976,260],{"class":108},[48,978,152],{"class":104},[48,980,265],{"class":108},[48,982,984,986,989,991,993,995,997,999,1001,1003,1005,1007,1010,1013,1015,1018],{"class":50,"line":983},12,[48,985,233],{"class":205},[48,987,988],{"class":108},"(df[[",[48,990,158],{"class":58},[48,992,72],{"class":108},[48,994,163],{"class":58},[48,996,72],{"class":108},[48,998,168],{"class":58},[48,1000,72],{"class":108},[48,1002,173],{"class":58},[48,1004,72],{"class":108},[48,1006,178],{"class":58},[48,1008,1009],{"class":108},"]].to_string(",[48,1011,1012],{"class":956},"index",[48,1014,131],{"class":104},[48,1016,1017],{"class":205},"False",[48,1019,1020],{"class":108},"))\n",[10,1022,1023,1024,1027,1028,1031,1032,1034,1035,1038],{},"One function applied once, immediately after ",[19,1025,1026],{},"read_excel",", fixes every heading for the rest of the program. ",[19,1029,1030],{},"NFKC"," normalisation is the heavy lifter: it turns the non-breaking space into an ordinary space and also folds other look-alikes, such as full-width letters from East Asian input methods, into their plain forms. It does not remove zero-width characters or change dashes, so two small substitutions handle those. Collapsing all whitespace — including the line break from Alt+Enter — into single spaces and stripping the ends finishes the job. After that, all five lookups work, and the printed ",[19,1033,252],{}," of every column shows plain names with nothing hidden in them. Applying the function with ",[19,1036,1037],{},"rename(columns=...)"," keeps the order of columns and touches nothing else in the DataFrame.",[583,1040,592,1046,592,1049,592,1052,592,1056,592,1063,592,1068,592,1073,592,1077,592,1082,592,1087,592,1092,592,1097,592,1101,592,1104,592,1108,592,1111,592,1114,592,1118,592,1121,592,1124,592,1128,592,1131,592,1136,592,1142,592,1145,592,1149,592,1152,592,1156,592,1159,592,1162,592,1166],{"viewBox":1041,"role":586,"ariaLabelledBy":1042,"xmlns":590,"style":1045},"4 0 752 126",[1043,1044],"ke-clean-t","ke-clean-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[594,1047,1048],{"id":1043},"What clean_header does to each heading",[598,1050,1051],{"id":1044},"The heading is normalised with NFKC, which turns no-break spaces into spaces. Zero-width characters are removed, dash variants become hyphens, all runs of whitespace including line breaks become single spaces, and the ends are stripped.",[602,1053],{"x":722,"y":605,"width":1054,"height":1055,"fill":608},"752","126",[602,1057],{"x":1058,"y":612,"width":1059,"height":1060,"rx":1061,"fill":656,"stroke":657,"style":1062},"20.0","115.2","94.0","12","stroke-width:2px",[623,1064,1030],{"x":1065,"y":1066,"style":1067},"77.6","50.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle",[623,1069,1072],{"x":1065,"y":1070,"style":1071},"69.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","no-break space",[623,1074,1076],{"x":1065,"y":1075,"style":1071},"87.2","→ space",[50,1078],{"x1":1079,"y1":1080,"x2":1081,"y2":1080,"stroke":657,"style":1062},"140.2","63.0","162.2",[1083,1084],"polygon",{"points":1085,"fill":1086},"166.2,63.0 157.2,68.0 157.2,58.0","#5b5cf0",[602,1088],{"x":1089,"y":612,"width":1059,"height":1060,"rx":1061,"fill":1090,"stroke":1091,"style":1062},"171.2","#fdefd8","var(--gold,#b4740a)",[623,1093,1096],{"x":1094,"y":1066,"style":1095},"228.8","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Drop",[623,1098,1100],{"x":1094,"y":1099,"style":1095},"67.8","zero-width",[623,1102,1103],{"x":1094,"y":1075,"style":1071},"\\u200b, BOM",[50,1105],{"x1":1106,"y1":1080,"x2":1107,"y2":1080,"stroke":657,"style":1062},"291.4","313.4",[1083,1109],{"points":1110,"fill":1086},"317.4,63.0 308.4,68.0 308.4,58.0",[602,1112],{"x":1113,"y":612,"width":1059,"height":1060,"rx":1061,"fill":1090,"stroke":1091,"style":1062},"322.4",[623,1115,1117],{"x":1116,"y":1066,"style":1095},"380.0","Dashes →",[623,1119,1120],{"x":1116,"y":1099,"style":1095},"hyphen",[623,1122,1123],{"x":1116,"y":1075,"style":1071},"en and em dash",[50,1125],{"x1":1126,"y1":1080,"x2":1127,"y2":1080,"stroke":657,"style":1062},"442.6","464.6",[1083,1129],{"points":1130,"fill":1086},"468.6,63.0 459.6,68.0 459.6,58.0",[602,1132],{"x":1133,"y":612,"width":1059,"height":1060,"rx":1061,"fill":1134,"stroke":1135,"style":1062},"473.6","#d9f4f1","var(--teal,#0f9488)",[623,1137,1141],{"x":1138,"y":1139,"style":1140},"531.2","41.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Collapse",[623,1143,1144],{"x":1138,"y":652,"style":1140},"whitespace",[623,1146,1148],{"x":1138,"y":1147,"style":1071},"78.5","line breaks,",[623,1150,1151],{"x":1138,"y":699,"style":1071},"tabs",[50,1153],{"x1":1154,"y1":1080,"x2":1155,"y2":1080,"stroke":657,"style":1062},"593.8","615.8",[1083,1157],{"points":1158,"fill":1086},"619.8,63.0 610.8,68.0 610.8,58.0",[602,1160],{"x":1161,"y":612,"width":1059,"height":1060,"rx":1061,"fill":1134,"stroke":1135,"style":1062},"624.8",[623,1163,1165],{"x":1164,"y":652,"style":1140},"682.4","Strip ends",[623,1167,1168],{"x":1164,"y":1147,"style":1071},"trailing spaces",[583,1170,592,1175,592,1178,592,1181,592,1184,592,1189,592,1195,592,1200,592,1204,592,1208,592,1212,592,1216,592,1219,592,1224,592,1228,592,1231,592,1234,592,1237],{"viewBox":1171,"role":586,"ariaLabelledBy":1172,"xmlns":590,"style":1045},"4 0 752 199",[1173,1174],"ke-strip-t","ke-strip-d",[594,1176,1177],{"id":1173},"str.strip() or a full clean",[598,1179,1180],{"id":1174},"Calling strip on the column names removes only ordinary spaces and line breaks at the ends. The full clean also turns no-break spaces into spaces, removes zero-width characters, maps dash variants to hyphens and collapses inner line breaks.",[602,1182],{"x":722,"y":605,"width":1054,"height":1183,"fill":608},"199",[602,1185],{"x":1058,"y":612,"width":1186,"height":1187,"rx":1188,"fill":1090,"stroke":1091,"style":1062},"351.0","167.0","14",[623,1190,1194],{"x":1191,"y":1192,"style":1193},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","df.columns.str.strip()",[50,1196],{"x1":1197,"y1":1198,"x2":1199,"y2":1198,"stroke":1091,"style":617},"36.0","57.0","355.0",[623,1201,1203],{"x":1191,"y":1202,"style":684},"77.0","trailing and leading spaces",[623,1205,1207],{"x":1191,"y":1206,"style":684},"103.0","line breaks at the ends only",[623,1209,1211],{"x":1191,"y":1210,"style":684},"129.0","leaves no-break spaces inside",[623,1213,1215],{"x":1191,"y":1214,"style":684},"155.0","leaves zero-width and dashes",[602,1217],{"x":1218,"y":612,"width":1186,"height":1187,"rx":1188,"fill":1134,"stroke":1135,"style":1062},"389.0",[623,1220,1223],{"x":1221,"y":1192,"style":1222},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","clean_header()",[50,1225],{"x1":1226,"y1":1198,"x2":1227,"y2":1198,"stroke":1135,"style":617},"405.0","724.0",[623,1229,1230],{"x":1221,"y":1202,"style":684},"NFKC: no-break → space",[623,1232,1233],{"x":1221,"y":1206,"style":684},"drops zero-width characters",[623,1235,1236],{"x":1221,"y":1210,"style":684},"dash variants → hyphen",[623,1238,1239],{"x":1221,"y":1214,"style":684},"collapses inner line breaks",[34,1241,1243],{"id":1242},"step-4-make-the-next-keyerror-explain-itself","Step 4 — Make the next KeyError explain itself",[39,1245,1247],{"className":95,"code":1246,"language":97,"meta":44,"style":44},"import difflib\n\ndef col(df, name):\n    \"\"\"Look up a column, and on failure say which existing column was probably meant.\"\"\"\n    if name in df.columns:\n        return df[name]\n    cleaned = {clean_header(c): c for c in df.columns}           # clean form -> real column name\n    close = difflib.get_close_matches(clean_header(name), list(cleaned), n=1, cutoff=0.6)\n    hint = f\" Did you mean {cleaned[close[0]]!r}?\" if close else \"\"\n    raise KeyError(f\"No column {name!r}. Columns are: {list(df.columns)}.{hint}\") from None\n\nraw = pd.read_excel(\"invoices.xlsx\")\ntry:\n    col(raw, \"Customer Name\")\nexcept KeyError as e:\n    print(e)\n",[19,1248,1249,1256,1260,1270,1275,1286,1294,1316,1349,1392,1446,1450,1463,1471,1481,1493],{"__ignoreMap":44},[48,1250,1251,1253],{"class":50,"line":51},[48,1252,105],{"class":104},[48,1254,1255],{"class":108}," difflib\n",[48,1257,1258],{"class":50,"line":118},[48,1259,122],{"emptyLinePlaceholder":121},[48,1261,1262,1264,1267],{"class":50,"line":125},[48,1263,319],{"class":104},[48,1265,1266],{"class":322}," col",[48,1268,1269],{"class":108},"(df, name):\n",[48,1271,1272],{"class":50,"line":143},[48,1273,1274],{"class":58},"    \"\"\"Look up a column, and on failure say which existing column was probably meant.\"\"\"\n",[48,1276,1277,1279,1282,1284],{"class":50,"line":184},[48,1278,485],{"class":104},[48,1280,1281],{"class":108}," name ",[48,1283,152],{"class":104},[48,1285,470],{"class":108},[48,1287,1288,1291],{"class":50,"line":193},[48,1289,1290],{"class":104},"        return",[48,1292,1293],{"class":108}," df[name]\n",[48,1295,1296,1299,1301,1304,1306,1308,1310,1313],{"class":50,"line":199},[48,1297,1298],{"class":108},"    cleaned ",[48,1300,131],{"class":104},[48,1302,1303],{"class":108}," {clean_header(c): c ",[48,1305,146],{"class":104},[48,1307,260],{"class":108},[48,1309,152],{"class":104},[48,1311,1312],{"class":108}," df.columns}           ",[48,1314,1315],{"class":829},"# clean form -> real column name\n",[48,1317,1318,1321,1323,1326,1328,1331,1333,1335,1337,1339,1342,1344,1347],{"class":50,"line":215},[48,1319,1320],{"class":108},"    close ",[48,1322,131],{"class":104},[48,1324,1325],{"class":108}," difflib.get_close_matches(clean_header(name), ",[48,1327,238],{"class":205},[48,1329,1330],{"class":108},"(cleaned), ",[48,1332,539],{"class":956},[48,1334,131],{"class":104},[48,1336,653],{"class":205},[48,1338,72],{"class":108},[48,1340,1341],{"class":956},"cutoff",[48,1343,131],{"class":104},[48,1345,1346],{"class":205},"0.6",[48,1348,140],{"class":108},[48,1350,1351,1354,1356,1359,1362,1364,1367,1369,1372,1375,1377,1380,1383,1386,1389],{"class":50,"line":230},[48,1352,1353],{"class":108},"    hint ",[48,1355,131],{"class":104},[48,1357,1358],{"class":104}," f",[48,1360,1361],{"class":58},"\" Did you mean ",[48,1363,346],{"class":345},[48,1365,1366],{"class":108},"cleaned[close[",[48,1368,605],{"class":205},[48,1370,1371],{"class":108},"]]",[48,1373,1374],{"class":104},"!r",[48,1376,358],{"class":345},[48,1378,1379],{"class":58},"?\"",[48,1381,1382],{"class":104}," if",[48,1384,1385],{"class":108}," close ",[48,1387,1388],{"class":104},"else",[48,1390,1391],{"class":58}," \"\"\n",[48,1393,1394,1397,1399,1401,1403,1406,1408,1411,1413,1415,1418,1420,1422,1425,1427,1429,1431,1434,1436,1438,1440,1443],{"class":50,"line":244},[48,1395,1396],{"class":104},"    raise",[48,1398,206],{"class":205},[48,1400,221],{"class":108},[48,1402,339],{"class":104},[48,1404,1405],{"class":58},"\"No column ",[48,1407,346],{"class":345},[48,1409,1410],{"class":108},"name",[48,1412,1374],{"class":104},[48,1414,358],{"class":345},[48,1416,1417],{"class":58},". Columns are: ",[48,1419,346],{"class":345},[48,1421,238],{"class":205},[48,1423,1424],{"class":108},"(df.columns)",[48,1426,358],{"class":345},[48,1428,32],{"class":58},[48,1430,346],{"class":345},[48,1432,1433],{"class":108},"hint",[48,1435,358],{"class":345},[48,1437,361],{"class":58},[48,1439,403],{"class":108},[48,1441,1442],{"class":104},"from",[48,1444,1445],{"class":205}," None\n",[48,1447,1448],{"class":50,"line":491},[48,1449,122],{"emptyLinePlaceholder":121},[48,1451,1452,1455,1457,1459,1461],{"class":50,"line":983},[48,1453,1454],{"class":108},"raw ",[48,1456,131],{"class":104},[48,1458,134],{"class":108},[48,1460,137],{"class":58},[48,1462,140],{"class":108},[48,1464,1466,1469],{"class":50,"line":1465},13,[48,1467,1468],{"class":104},"try",[48,1470,190],{"class":108},[48,1472,1474,1477,1479],{"class":50,"line":1473},14,[48,1475,1476],{"class":108},"    col(raw, ",[48,1478,163],{"class":58},[48,1480,140],{"class":108},[48,1482,1484,1487,1489,1491],{"class":50,"line":1483},15,[48,1485,1486],{"class":104},"except",[48,1488,206],{"class":205},[48,1490,209],{"class":104},[48,1492,212],{"class":108},[48,1494,1496,1499],{"class":50,"line":1495},16,[48,1497,1498],{"class":205},"    print",[48,1500,1501],{"class":108},"(e)\n",[10,1503,1504,1505,1507,1508,1511,1512,1515,1516,1519,1520,1523,1524,1526,1527,1530,1531,32],{},"Even with clean headings, a column can be renamed at source — ",[14,1506,75],{}," becomes ",[14,1509,1510],{},"Customer"," next quarter. A small lookup helper that lists the available columns and suggests the closest one turns a bare ",[19,1513,1514],{},"KeyError"," into a message that says what to change. ",[19,1517,1518],{},"difflib.get_close_matches"," compares the cleaned versions, so it finds the intended column even when the only difference is an invisible character, which is exactly the case where a human would struggle. The suggestion is then shown as the ",[14,1521,1522],{},"real"," column name with ",[19,1525,252],{},", so the message itself reveals the culprit: ",[14,1528,1529],{},"Did you mean 'Customer\\xa0Name'?"," points straight at the non-breaking space. For a full check of all expected columns before processing starts, see ",[28,1532,1534],{"href":1533},"\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",[34,1536,1538],{"id":1537},"where-the-characters-come-from","Where the characters come from",[10,1540,1541,1542,1545,1546,32],{},"Knowing the source helps prevent the problem upstream. Trailing spaces come from people typing; they are the most common cause and the easiest to miss. Non-breaking spaces come from copying text out of web pages, PDFs and many ERP and CRM exports, which use them to stop headings wrapping. Line breaks appear when someone wraps a long heading with Alt+Enter to make a narrow column look tidy. Zero-width spaces and byte-order marks ride along with text pasted from websites and chat tools, or sit at the very start of CSV files saved by some applications. En and em dashes are introduced by autocorrect in Word and Outlook and then pasted into Excel. None of them is visible in a cell, which is why the fix has to be in code. The same problems affect values as well as headings — a customer called ",[14,1543,1544],{},"Acme Ltd"," with a trailing non-breaking space will not match in a merge — and the same cleaning function can be applied to key columns, as described in ",[28,1547,1549],{"href":1548},"\u002Fadvanced-data-transformation-and-cleaning\u002Fmerging-and-joining-excel-dataframes\u002Fmerge-on-keys-that-differ-in-case-and-spacing-with-pandas\u002F","Merge on Keys That Differ in Case and Spacing with pandas",[34,1551,1553],{"id":1552},"prevent-it-in-the-files-you-produce","Prevent it in the files you produce",[10,1555,1556,1557,1560,1561,1564],{},"When your own code writes workbooks that other scripts read, write clean headings in the first place: apply the same function to the DataFrame's columns before ",[19,1558,1559],{},"to_excel",", and avoid wrapping headings with embedded line breaks — set ",[19,1562,1563],{},"wrap_text"," on the header cells instead, which wraps the display without changing the text. When a source system keeps producing odd headings, it is often worth a short message to whoever owns the export; a trailing space in a report template takes seconds to remove at source and saves every consumer from working around it.",[34,1566,1568],{"id":1567},"common-pitfalls","Common pitfalls",[1570,1571,1572,1584,1593,1599,1607],"ul",{},[1573,1574,1575,1579,1580,85,1582,32],"li",{},[1576,1577,1578],"strong",{},"Printing the columns list."," It hides most invisible characters; use ",[19,1581,271],{},[19,1583,559],{},[1573,1585,1586,1592],{},[1576,1587,1588,1589,32],{},"Only calling ",[19,1590,1591],{},".str.strip()"," It does not remove no-break or zero-width spaces between words.",[1573,1594,1595,1598],{},[1576,1596,1597],{},"Cleaning in several places."," Normalise once after reading and use the clean names everywhere.",[1573,1600,1601,1604,1605,32],{},[1576,1602,1603],{},"Forgetting dashes."," An en dash looks like a hyphen; map dash variants to ",[19,1606,440],{},[1573,1608,1609,1612],{},[1576,1610,1611],{},"Bare KeyErrors."," List the available columns and suggest the closest match.",[34,1614,1616],{"id":1615},"conclusion","Conclusion",[10,1618,1619,1620,1622,1623,85,1625,1627,1628,1630],{},"A ",[19,1621,1514],{}," for a column you can see means the heading contains a character you cannot: a trailing space, a non-breaking space, a line break, a zero-width space or a different dash. Reveal them with ",[19,1624,271],{},[19,1626,559],{},", normalise every heading once after ",[19,1629,1026],{}," with NFKC plus a few substitutions, and make lookups fail with the list of real columns and the nearest match.",[34,1632,1634],{"id":1633},"frequently-asked-questions","Frequently asked questions",[10,1636,1637,1640],{},[1576,1638,1639],{},"Why does pandas say KeyError when the column exists in Excel?","\nThe heading in the file contains an invisible character, such as a trailing or non-breaking space or a line break, so the string differs from the one in your code.",[10,1642,1643,1646,1647,1650,1651,1654],{},[1576,1644,1645],{},"How do I see hidden characters in pandas column names?","\nPrint ",[19,1648,1649],{},"[repr(c) for c in df.columns]",", and use ",[19,1652,1653],{},"unicodedata.name(ch)"," on unusual characters to identify them.",[10,1656,1657,1660,1661,1664],{},[1576,1658,1659],{},"How do I remove non-breaking spaces from column names?","\nApply ",[19,1662,1663],{},"unicodedata.normalize(\"NFKC\", name)",", which turns them into ordinary spaces, then collapse and strip whitespace.",[10,1666,1667,1670],{},[1576,1668,1669],{},"Does df.columns.str.strip() fix it?","\nOnly for ordinary spaces at the ends; it leaves non-breaking spaces between words, zero-width characters and line breaks inside the name.",[34,1672,1674],{"id":1673},"related","Related",[1570,1676,1677,1683,1690,1697],{},[1573,1678,1679,1680,1682],{},"Up one level: ",[28,1681,31],{"href":30}," — common errors and fixes.",[1573,1684,1685,1689],{},[28,1686,1688],{"href":1687},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcleaning-excel-data-with-pandas\u002Fstandardise-excel-column-names-with-pandas\u002F","Standardise Excel Column Names with pandas"," — consistent naming conventions.",[1573,1691,1692,1696],{},[28,1693,1695],{"href":1694},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-duplicate-column-names-when-reading-excel\u002F","Fix Duplicate Column Names When Reading Excel with pandas"," — another header problem.",[1573,1698,1699,1703],{},[28,1700,1702],{"href":1701},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-unnamed-0-columns-when-reading-excel\u002F","Fix \"Unnamed: 0\" Columns When Reading Excel with pandas"," — headers in the wrong place.",[1705,1706,1707],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}",{"title":44,"searchDepth":118,"depth":118,"links":1709},[1710,1711,1712,1713,1714,1715,1716,1717,1718,1719,1720],{"id":36,"depth":118,"text":37},{"id":91,"depth":118,"text":92},{"id":297,"depth":118,"text":298},{"id":779,"depth":118,"text":780},{"id":1242,"depth":118,"text":1243},{"id":1537,"depth":118,"text":1538},{"id":1552,"depth":118,"text":1553},{"id":1567,"depth":118,"text":1568},{"id":1615,"depth":118,"text":1616},{"id":1633,"depth":118,"text":1634},{"id":1673,"depth":118,"text":1674},"2026-10-02","pandas raises KeyError for a column you can see in the sheet: the heading has a trailing space, a non-breaking space, a line break typed with Alt+Enter, a zero-width character or a different dash. Show the hidden characters with repr and unicodedata, suggest the nearest real column name, normalise the headings once after reading, and make the lookup fail with a useful message.","md",[1725,1727,1729,1731],{"q":1639,"a":1726},"The heading in the file contains an invisible character, such as a trailing or non-breaking space or a line break, so the string differs from the one in your code.",{"q":1645,"a":1728},"Print [repr(c) for c in df.columns], and use unicodedata.name(ch) on unusual characters to identify them.",{"q":1659,"a":1730},"Apply unicodedata.normalize(\"NFKC\", name), which turns them into ordinary spaces, then collapse and strip whitespace.",{"q":1669,"a":1732},"Only for ordinary spaces at the ends; it leaves non-breaking spaces between words, zero-width characters and line breaks inside the name.","pandas keyerror column name space",null,{"breadcrumb":1736},[1737,1740,1743],{"name":1738,"item":1739},"Home","\u002F",{"name":1741,"item":1742},"Getting Started with Python Excel Automation","\u002Fgetting-started-with-python-excel-automation\u002F",{"name":31,"item":30},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names",{"title":1746,"description":1747},"Fix KeyError from Spaces in Excel Column Names in pandas","Fix pandas KeyError for a column that looks right: reveal trailing, non-breaking and zero-width spaces, line breaks and dashes in Excel headings, suggest close matches and normalise once.","fix-keyerror-from-spaces-in-excel-column-names","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-keyerror-from-spaces-in-excel-column-names\u002Findex",[1751,1752],"troubleshooting","reading-excel","how-to","mLITqhXks8RnC10ZW3ZLZQuAxHszyOFd6B79Rl4ktGs",[1756,1760],{"title":1757,"path":1758,"stem":1759,"children":-1},"Fix Invalid Worksheet Name Errors When Writing Excel","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-invalid-worksheet-name-errors","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-invalid-worksheet-name-errors\u002Findex",{"title":1761,"path":1762,"stem":1763,"children":-1},"Fix \"Filesize would require ZIP64 extensions\" (LargeZipFile) in XlsxWriter","\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-largezipfile-error-in-xlsxwriter","getting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors\u002Ffix-largezipfile-error-in-xlsxwriter\u002Findex",[1765,1767],{"path":1766,"title":1741},"\u002Fgetting-started-with-python-excel-automation",{"path":1768,"title":31},"\u002Fgetting-started-with-python-excel-automation\u002Ftroubleshooting-common-python-excel-errors",[],[1771,1775,1778,1781,1784,1787],{"path":1772,"title":1773,"kind":1774},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation\u002Fhandle-permission-denied-when-writing-excel-in-python","Handle Permission Denied When Writing Excel in Python","fix",{"path":1776,"title":1777,"kind":1753},"\u002Fgetting-started-with-python-excel-automation\u002Fusing-openpyxl-for-excel-file-manipulation\u002Fwhat-openpyxl-loses-when-saving-an-existing-workbook","What openpyxl Loses When You Save an Existing Workbook",{"path":1779,"title":1780,"kind":1774},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Ffix-excel-serial-numbers-showing-instead-of-dates","Fix Excel Serial Numbers Showing Instead of Dates",{"path":1782,"title":1783,"kind":1753},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fhandle-daylight-saving-time-in-excel-timestamps","Handle Daylight Saving Time in Excel Timestamps",{"path":1785,"title":1786,"kind":1753},"\u002Fautomating-reporting-workflows\u002Fautomating-excel-online-with-microsoft-graph\u002Fhandle-microsoft-graph-throttling-for-excel","Handle Microsoft Graph Throttling in Excel Automation",{"path":1788,"title":1789,"kind":1790},"\u002Fautomating-reporting-workflows\u002Ferror-handling-and-logging-in-excel-automation","Error Handling and Logging in Excel Automation","guide",1790972065873]