pandas KeyError but the column exists (tested)
If df["price"] raises KeyError: 'price' but you can see the column, the real name is different: usually " price" with a space, "id" with an Excel BOM, or the column was moved into the index. We tested each cause and fix on pandas 2.3.3 and 3.1.0.
First: print the real column names
Run print(list(df.columns)). print(df) and df.head() hide leading spaces and invisible characters, the list repr shows them. The three causes below cover most cases where the column "is clearly there".
1. A space in the header
import io
import pandas as pd
data = "name, price\nwidget, 3\n" # note the space after the comma
df = pd.read_csv(io.StringIO(data))
print(list(df.columns))
try:
df["price"]
except KeyError as e:
print("KeyError:", e)
df.columns = df.columns.str.strip() # fix 1: strip existing headers
print(df["price"].tolist())
df2 = pd.read_csv(io.StringIO(data), skipinitialspace=True) # fix 2: at read time
print(list(df2.columns))
Output (identical on pandas 2.3.3 and 3.1.0):
['name', ' price']
KeyError: 'price'
[3]
['name', 'price']
CSV files written as name, price give a column called " price". Fix it once after loading with df.columns = df.columns.str.strip(), or at read time with skipinitialspace=True (that one only removes spaces after the delimiter, not trailing ones).
2. A BOM from Excel glued to the first column
import pandas as pd
with open("excel.csv", "w", encoding="utf-8-sig") as f: # what Excel "CSV UTF-8" writes
f.write("id,total\n1,9\n")
df = pd.read_csv("excel.csv", encoding="latin-1")
print(list(df.columns))
try:
df["id"]
except KeyError as e:
print("KeyError:", e)
df = pd.read_csv("excel.csv", encoding="utf-8-sig")
print(list(df.columns), df["id"].tolist())
Output (identical on pandas 2.3.3 and 3.1.0):
['id', 'total']
KeyError: 'id'
['id', 'total'] [1]
Excel's "CSV UTF-8" format starts the file with a byte order mark. pandas removes it with the default encoding (we checked: pd.read_csv("excel.csv") gives ['id', 'total']), but if you passed encoding="latin-1" or "cp1252" to get past a decode error, the BOM becomes  in the first header and df["id"] fails. Use encoding="utf-8-sig". Python's own csv module with encoding="utf-8" shows it as '\ufeffid' instead.
3. The column became the index, or the case differs
import pandas as pd
df = pd.DataFrame({"user": ["a", "b"], "score": [1, 2]}).set_index("user")
print(list(df.columns), df.index.name)
try:
df["user"]
except KeyError as e:
print("KeyError:", e)
print(df.index.tolist()) # it is the index now
print(df.reset_index()["user"].tolist()) # or move it back to a column
df = pd.DataFrame({"Price": [1]})
try:
df["price"]
except KeyError as e:
print("KeyError:", e)
df.columns = df.columns.str.strip().str.lower()
print(list(df.columns))
Output (identical on pandas 2.3.3 and 3.1.0):
['score'] user
KeyError: 'user'
['a', 'b']
['a', 'b']
KeyError: 'price'
['price']
After set_index("user") (or read_csv(..., index_col=0), or a groupby), user is no longer in df.columns: use df.index or df.reset_index(). Column lookup is case-sensitive, so "Price" and "price" are different keys; normalise with df.columns.str.strip().str.lower().
Related: KeyError: 0 in pandas · KeyError but the key exists (dicts) · SettingWithCopyWarning.
Frequently asked questions
Why does pandas say KeyError when the column exists?
The stored name differs from what you typed: a leading/trailing space, a BOM, different case, or the column is now the index. print(list(df.columns)) shows the real names.
How do I remove spaces from pandas column names?
df.columns = df.columns.str.strip(), or read_csv(..., skipinitialspace=True) for spaces after the delimiter.
Why is the first column name id?
The file starts with a UTF-8 BOM and was read as latin-1 or cp1252. Read it with encoding='utf-8-sig'.
Written with AI assistance. Measured on 2026-10-23 in the python:3-alpine Docker image (Python 3.14) with pandas 2.3.3 and pandas 3.1.0. Each block was run verbatim on both versions; the outputs were identical and are pasted unedited. PlainHeaders is our extension; Python and pandas are free.