You called df.loc[index_label] on a DataFrame and Python raised KeyError. The label looks valid, you can even see it in df.index. Why is pandas saying it doesn’t exist?
This guide walks through the 5 most common causes of pandas KeyError on .loc and .iloc, with the exact diagnostic command to run for each one.

📌 Quick answer: Run print(df.index.dtype, df.index[:5].tolist()) first. If your index is integers but you’re passing a string (or vice versa), that’s your bug. If the label is genuinely missing, use df.loc[df.index.intersection([label])] for safe partial match, or use df.reindex([label]) which returns NaN rows instead of raising.
Cause 1: Index dtype mismatch (most common)
You pass an integer index label, but the actual index dtype is string (or vice versa).
import pandas as pd
df = pd.read_csv("users.csv") # index loaded as string "1", "2"
df.loc[1] # ❌ KeyError: 1 (looking for int, index has strings)
df.loc["1"] # ✓ matches
# Diagnose
print(df.index.dtype) # object (i.e. string)
print(df.index[:3]) # Index(['1', '2', '3'], dtype='object')
# Force integer index at load time
df = pd.read_csv("users.csv", index_col=0)
df.index = df.index.astype(int)
df.loc[1] # ✓ now works
Cause 2: Label was dropped by filter
After filtering, the index label you want no longer exists in the filtered DataFrame.
df = pd.DataFrame({"score": [85, 60, 75]}, index=["A", "B", "C"])
high = df[df["score"] > 70]
high.loc["B"] # ❌ KeyError: 'B' (B was filtered out)
# Safe pattern: use intersection
high.loc[high.index.intersection(["B"])] # ✓ empty DataFrame, no error
# Or reindex (returns NaN for missing labels)
high.reindex(["B"]) # ✓ row with NaN values
Cause 3: Whitespace or case mismatch in string labels
Common with CSV-loaded data where labels have trailing spaces or different cases than expected.
df = pd.read_csv("data.csv")
df.loc["Alice"] # ❌ KeyError: 'Alice'
# Diagnose: check for whitespace
print(repr(df.index[0])) # 'Alice ' or ' Alice\n' or 'alice'
# Fix: normalize the index
df.index = df.index.str.strip().str.title()
df.loc["Alice"] # ✓ works now
Cause 4: MultiIndex requires tuple access
If your DataFrame has a MultiIndex, you must pass a tuple, not a single label.
df = pd.DataFrame(
{"sales": [100, 200, 150]},
index=pd.MultiIndex.from_tuples([
("PH", "Manila"), ("PH", "Cebu"), ("US", "Texas")
], names=["country", "city"])
)
df.loc["Manila"] # ❌ KeyError (not a country)
df.loc["PH"] # ✓ slice of country PH
df.loc[("PH", "Manila")] # ✓ specific row
df.loc[("PH", "Manila"), "sales"] # ✓ scalar value
Cause 5: iloc out of position bounds
iloc is position-based and raises IndexError (not KeyError), but the confusion overlaps. Reach for min(i, len(df)-1) or guard with if len(df) > i:.
df.iloc[100] # ❌ IndexError if df has fewer than 101 rows
# Safe pattern
if len(df) > 100:
row = df.iloc[100]
else:
row = None
# Or use .head() / .tail() for safe partial access
last_5 = df.tail(5)
first_5 = df.head(5)
Prevention: 4 Defensive Patterns
- Always check index dtype first:
print(df.index.dtype) - Use .reindex() for “give me these labels with NaN for missing”
- Use .loc[df.index.intersection([labels])] for safe partial selection
- For position-based access, guard with
if len(df) > ior use.head() / .tail()
Related Guides
- Pandas KeyError on column name (full guide)
- Pandas iloc IndexError out of bounds
- 5 Safe dict-lookup Patterns
- All KeyError fixes
Debugging checklist for .loc / .iloc KeyError
- Confirm you are using
.locfor label-based access and.ilocfor position-based access. Mixing them is the top cause of KeyError. - Print
df.indexbefore the access. If your index is a range but you are passing a string, you have a mismatch. - For column access with
.loc[row, col], confirm both row label and column label exist.
Safe .loc / .iloc access
# Label-based (.loc): both index label and column label must exist
if "customer_42" in df.index and "email" in df.columns:
value = df.loc["customer_42", "email"]
# Position-based (.iloc): both must be integers in range
if 0 <= 5 < len(df) and 0 <= 2 < len(df.columns):
value = df.iloc[5, 2]
# Safer alternative: .at (single value, label-based)
try:
value = df.at["customer_42", "email"]
except KeyError:
value = NoneWhen to reach for a different accessor
- .at / .iat are faster for single-cell access.
- .get works on Series but not on DataFrame rows. Use
df["col"].get(key, default). - Boolean masking avoids KeyError entirely:
df[df["id"] == 42].
Related pandas KeyError patterns to watch for
- KeyError on .groupby with a column that does not exist. Verify with
list(df.columns). - KeyError on .drop when the column was already dropped earlier in the pipeline.
- KeyError on .rename when passing
errors="raise"and the source column does not exist. Useerrors="ignore"for optional renames. - KeyError after .set_index when the promoted column is still expected in
df.columns. Usedf.reset_index()to bring it back.
Pandas KeyError patterns
KeyError in pandas fires when you access a column, index label, or row that does not exist. Because pandas uses labels (not just positions), a KeyError on df["column_name"] means the column string literally is not in df.columns.
Common triggers
- Column not in DataFrame. Whitespace, case, or typo in the column name. Print df.columns.tolist() to see the actual names.
- Index label not present. df.loc[label] fails if label is not in df.index.
- Renamed on load. read_csv may rename columns if header parsing was wrong.
- MultiIndex access. df.loc[key] on MultiIndex needs a tuple, not a scalar.
- Merged column disappeared. After merge, only the join key remains as one column — the right-side extra columns get suffixed (_x, _y).
Diagnostic pattern
# BAD — whitespace in column name
import pandas as pd
df = pd.read_csv("data.csv")
print(df["price"]) # KeyError if actual name is " price" or "Price"
# GOOD — inspect columns first
print(df.columns.tolist()) # ['name', ' price', 'quantity']
# Normalize names on load
df.columns = df.columns.str.strip().str.lower()
print(df["price"]) # now works
Best practices
- Normalize column names on load. Strip, lower, replace spaces with underscores.
- Use df.get(“col”). Returns None instead of raising, similar to dict.get.
- Check with “col in df.columns” before accessing.
- Use assert on expected columns. Fails fast in data pipelines.
Official documentation
Quick step-by-step summary (click to expand)
- Use loc for label-based access. df.loc[100] returns the row where the index label is 100, not the 101st row. Use for meaningful indexes like dates or IDs.
- Use iloc for positional access. df.iloc[0] returns the first row regardless of the index label. Use for first N or last N rows.
- Check df.index.dtype before deciding. If df.index.dtype is int64 with sequential labels, loc and iloc happen to match. Otherwise pick based on intent.
- Print df.index before the failing call. Seeing the actual index values reveals whether your label exists (loc) or is out of range (iloc).
Frequently Asked Questions
Why does df.loc raise KeyError when the label is in df.index?
Usually dtype mismatch. df.loc[1] fails if the index dtype is object (string), because pandas does exact-type matching. Check df.index.dtype, and either convert your query to match (df.loc[“1”]) or convert the index (df.index = df.index.astype(int)).
What’s the difference between .loc and .iloc?
.loc uses label-based indexing: df.loc[“Alice”] looks up the row with index label “Alice”. .iloc uses position-based indexing: df.iloc[0] always returns the first row regardless of label. .loc raises KeyError on missing labels; .iloc raises IndexError on out-of-range positions.
How do I safely query a DataFrame for a label that might not exist?
Three options: (1) df.loc[df.index.intersection([label])] returns empty if missing, (2) df.reindex([label]) returns NaN row if missing, (3) if label in df.index: df.loc[label] for explicit branching.
Why does MultiIndex access fail with KeyError?
A MultiIndex requires tuple access: df.loc[(“PH”, “Manila”)], not df.loc[“Manila”]. To slice by the outer level, pass just the outer label: df.loc[“PH”]. To go deeper, use df.xs(“Manila”, level=”city”).
How do I check if a label exists before .loc access?
Use the in operator: if label in df.index: … For column existence: if col in df.columns. Both are O(1) hash lookups on standard pandas indexes.
