potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

Python 3&Pandas 1.4.4 データフレームは[]とloc[]でタプルによる列指定結果が違う

Pandas 1.4.4ではデータフレームの列名による列指定に[]loc[]が使えますが、これらはタプルで列名を指定したときの動作が異なります。[]はタプルをひとつのキーとして扱い、loc[]はデータフレームの持つ列名により動作を変えます。前者はデータフレームがハッシュ化できるオブジェクト(辞書のキーになれるオブジェクト?)を列名として扱う動作通りですが、後者は複雑です。

ここでは[]loc[]の違いを紹介しますが、結論としてはデータフレームのインデックス参照[]ではリストを使う(タプルは使わない)方が良さそうです。リストの方が多少コストが大きいですが、いくつかの列名を指定する程度であれば普通は無視できる程度だと思います。列がタプルを含む場合はもちろんタプルも使います。

# []とloc[]の動作差
import pandas as pd

df = pd.DataFrame({"A": [0, 1, 2], "B": [3, 4, 5]})

print(df[["A", "B"]]) # Success.
#print(df[("A", "B")]) # KeyError:データフレームは列名("A", "B")を持たない。
print(df.loc[:, ["A", "B"]]) # Success.
print(df.loc[:, ("A", "B")]) # Success!?:列Aと列Bが選択される。
# loc[]の列名による動作差
import pandas as pd

df1 = pd.DataFrame({
    "A": [1],
    "B": [2],
    ("A", "B"): [3],
    ("A", "B", "A"): [4],
    ("A", "B", "B"): [5],
    ("B", "A", "B"): [6]})

print(df1)
#   A  B  (A, B)  (A, B, A)  (A, B, B)  (B, A, B)
#0  1  2       3          4          5          6

print(df1.loc[:,"A"])
#列Aをシリーズとして返す。
#0    1
#Name: A, dtype: int64

print(df1.loc[:,("A",)])
#列Aをデータフレームとして返す。
#   A
#0  1

print(df1.loc[:,("A","B")])
print(df1.loc[:,("A","B",)])
#列Aと列Bをデータフレームとして返す。
#注意:列("A", "B")ではない。
#   A  B
#0  1  2

df2 = pd.DataFrame({
    ("A", "B"): [3],
    ("A", "B", "A"): [4],
    ("A", "B", "B"): [5],
    ("B", "A", "B"): [6]})

print(df2.loc[:,"A"])
print(df2.loc[:,("A",)])
#列("A"(, ...))に該当する列を返す。
#注意:列Aがある場合と動作が異なる。
#    B      
#  NaN  A  B
#0   3  4  5

print(df2.loc[:,("A","B")])
print(df2.loc[:,("A","B")])
#列("A", "B"(, ...))に該当する列を返す。
#注意:列("A", "B")だけではない。
#   NaN  A  B
#0    3  4  5

print(df2.loc[:,("A","B",pd.NA)])
print(df2.loc[:,("A","B",pd.NA,)])
#列("A", "B")に該当するシリーズを返す。
#0    3
#Name: (A, B, nan), dtype: int64