pandasでKeyErrorが出るときの原因と直し方。列名の空白と全角に気をつける[Python]
CSVを読み込んで列を取り出そうとしたら、確かにあるはずの列名でこれが出ることがある。
KeyError: 'price'pandasだとこういう長いメッセージになることもある。
KeyError: "['price'] not in index"画面で見ればちゃんとpriceという列がある。それなのに見つからないと言われる。原因の多くは、目に見えない文字が混ざっていることになる。
まず列名の実体を確認する
推測する前に、実際にどんな列名になっているかを確かめる。表示するだけだと空白が見えないので、リストにして出すのがコツになる。
print(df.columns.tolist())これで['price ', ' name', 'price\n']のように、前後の空白や改行が見える形で出てくる。さらに厳密に見たいなら、1文字ずつ確認する。
for c in df.columns: print(repr(c), [hex(ord(ch)) for ch in c])reprを使うと'price 'のようにクォートが付いて表示されるので、空白の有無が確実にわかる。
よくある犯人はこのあたりになる。
・前後の半角スペース。'price 'と'price'は別物
・全角スペース。 として混ざっていることがある
・BOM。UTF-8のCSVの先頭列にが付いて'id'になる
・改行。'price\n'のように末尾に残る
・全角の英字。'price'は'price'ではない
列名を一括で正規化する
原因を特定したら、読み込み直後に列名をそろえてしまうのが確実になる。
df.columns = df.columns.str.strip()全角スペースも含めて落としたいなら、除去する文字を指定する。
df.columns = df.columns.str.strip().str.replace(" ", "", regex=False)大文字小文字を統一しておくと、あとの参照が楽になる。
df.columns = df.columns.str.strip().str.lower()BOMは読み込み時のエンコーディング指定で解決できる。
df = pd.read_csv("data.csv", encoding="utf-8-sig")utf-8ではなくutf-8-sigにすると、BOMを自動的に取り除いてくれる。Excelで保存したCSVはBOM付きになりやすいので、日本語環境ではこちらを既定にしておくと事故が減る。
日本語のCSVで文字化けする場合は、そもそものエンコーディングが原因のこともある。その話はUnicodeDecodeErrorの対処にまとめた。
ヘッダー行がずれている
先頭に説明行やタイトル行が入っているファイルだと、1行目がヘッダーとして読まれず、列名がUnnamed: 0のようになる。
print(df.columns.tolist())# ['売上データ', 'Unnamed: 1', 'Unnamed: 2']この場合は、どの行がヘッダーかを指定する。
df = pd.read_csv("data.csv", header=2) # 3行目をヘッダーにするExcelファイルならskiprowsを使うこともある。
df = pd.read_excel("data.xlsx", skiprows=2)存在しない列を安全に扱う
列があるかどうかわからない状態で処理するなら、事前に確認する。
if "price" in df.columns: total = df["price"].sum()複数の列をまとめて選ぶとき、一部が無いだけで落ちるのを避けたいなら、積集合を取る。
want = ["id", "name", "price"]cols = [c for c in want if c in df.columns]sub = df[cols]無い列を欠損として作りたい場合はreindexが使える。
sub = df.reindex(columns=want) # 無い列は NaN で作られるloc と iloc の取り違え
列名ではなく行の指定でKeyErrorが出ることもある。locはラベル、ilocは位置を指す。
df.loc[0] # インデックスラベルが 0 の行。ラベルが無いと KeyErrordf.iloc[0] # 先頭の行。常に動くフィルタやsort_valuesをしたあとはインデックスが飛び飛びになるので、loc[0]が存在しないことがある。位置で取りたいならilocを使うか、インデックスを振り直す。
df = df.reset_index(drop=True)辞書のKeyErrorの場合
pandasではなく素の辞書でKeyErrorが出ているなら、getを使うと既定値を返せる。
value = d.get("price", 0) # 無ければ 0まとめ
・列があるのにKeyErrorが出るときは、まずdf.columns.tolist()で実体を確認する。reprで見ると空白がわかる
・原因の多くは前後の空白、全角スペース、BOM、改行、全角英字
・読み込み直後にdf.columns = df.columns.str.strip()で正規化しておくと事故が減る
・Excel由来のCSVはencoding="utf-8-sig"にするとBOMが消える
・タイトル行があるファイルはheader=やskiprows=でヘッダー位置を指定する
・複数列の選択はreindex(columns=...)にすると、無い列があっても落ちない
・行のKeyErrorはlocとilocの取り違えが多い。フィルタ後はreset_index(drop=True)を挟む