フューチャー技術ブログ

pandas 1.0以降での変更点を一部紹介します

SAIGの小橋昌明です。業務ではずっとPythonを書いています。

今回は Python連載 の6日目です。

Pythonを使ってデータ分析をする上で無くてはならないのがpandasです。リリースノートを見てみると、メジャーアップデートによる1.0.0 がリリースされたのが2020年の1月で、現時点での最新は1.3.3です。

ただ、1.0以降の機能で私自身が使ってるものは何だろうかと考えてみると、query関数のエスケープシーケンスだけしか使っていない気がします。DataFrameから条件を指定してデータを抽出するquery関数は、列名に記号を含んでいたりすると上手く動かないことがありますが、v1.0以降はbacktick記号 (` ` )で囲むと動くようになりました。リリースノートはこちら

しかし上記は新しい機能のごく一部にすぎません。せっかくバージョンアップされているのに、機能を使わないのはちょっともったいない……

今回、pandas 1.0以降の変更点を調べたので、2つ紹介します。

なお、1.0以降の最も大きい機能追加はpd.NAが導入されたことだと思います。が、これに関してはpandas 1.2.0+ での pd.NA の特徴という記事に非常に詳しく書かれているので、本記事では述べません。

重複したラベルを許容しないオプション指定(v1.2~)

※試験的な機能(Experimental Feature)

リリースノートはこちら、User Guide内の説明はこちらです。
このセクションのサンプルコードは全て、執筆時点の最新であるpandas 1.3.3を用いています。

pandasのindexやcolumnには重複したラベルを付けることができます。

pd.Series([1, 2], index=['a', 'a'])

---

a 1
a 2
dtype: int64

v1.2以降では、set_flags関数を用いて、ラベル重複を許容しないようにすることができます。重複していた場合はDuplicateLabelErrorというエラーが上がります。

pd.Series([1, 2], index=['a', 'a']).set_flags(allows_duplicate_labels=False)

---

DuplicateLabelError: Index has duplicates.
positions
label
a [0, 1]

ラベルに重複があるかどうかを調べるだけならば、is_unique関数を使えばよいです。こちらは1.0よりも前からある機能です。

pd.Series([1, 2], index=['a', 'a']).index.is_unique

---

False

ラベルが重複していると処理時間が長くなってしまうので、concatなどでDataFrameを作った後はラベルを振り直した方が良いのでしょう。

さて、allows_duplicate_labels の値は操作を通じて伝播(propagate)していきます。
ただ試験的な機能につき、伝播しない操作も多く存在するため注意しましょう、とドキュメントには書いてあります。

リリースノートにある例です。

a = (
pd.Series([1, 2], index=['a', 'b'])
.set_flags(allows_duplicate_labels=False)
)
a

---

a 1
b 2
dtype: int64
# 重複ラベルが発生する操作
a.reindex(['a', 'b', 'a'])

---

DuplicateLabelError: Index has duplicates.
positions
label
a [0, 2]

新しいSeriesを作るreindex関数の返り値にも、allows_duplicate_labels=Falseが伝播して設定されています。

では、試しにmerge関数の例をやってみましょう。

df1 = pd.DataFrame({
'col_A' : ['foo', 'bar', 'baz'],
'col_B' : [10, 20, 30],
})
df1 = df1.set_flags(allows_duplicate_labels=False) # set_flags()関数は新たなDataFrameを返すので、再度代入する必要がある
df1

---

col_A col_B
0 foo 10
1 bar 20
2 baz 30
df2 = pd.DataFrame({
'col_A' : ['foo', 'foo', 'bar'],
'col_C' : [0.1, 0.2, 0.3],
}, index=[1, 1, 2]
)
df2
---

col_A col_C
1 foo 0.1
1 foo 0.2
2 bar 0.3

col_Aを基準にmergeすると、結果のDataFrameのindexは新たに0から振り直されるので、重複は発生しません。したがって、indexを基準にmergeします。

df1.merge(df2, left_index=True, right_index=True)

---

col_A_x col_B col_A_y col_C
1 bar 20 foo 0.1
1 bar 20 foo 0.2
2 baz 30 bar 0.3

エラーが発生せずに、重複ラベルを含むDataFrameが作れてしまいました。allows_duplicate_labelsの値を確認します。

temp = df1.merge(df2, left_index=True, right_index=True)
temp.flags.allows_duplicate_labels

---

True

どうやら、mergeをするとallows_duplicate_labelsの値は伝播されないようです。まだ挙動には注意が必要そうですね。

存在しないラベルに対する.locの挙動(v1.0~、v1.1~)

df1 = pd.DataFrame({
'col_A' : ['foo', 'bar', 'baz'],
'col_B' : [10, 20, 30],
'col_C' : [0.1, 0.2, 0.3],
})
df1

---

col_A col_B col_C
0 foo 10 0.1
1 bar 20 0.2
2 baz 30 0.3

ここで、.loc関数で複数の行を指定し、その中に存在しない行名が入っている場合の挙動を示します。
まずはpandas 1.0.0より前の挙動から。

# pandas 0.25.3
df1.loc[[1, 999]]

---

FutureWarning:
Passing list-likes to .loc or [] with any missing label will raise
KeyError in the future, you can use .reindex() as an alternative.

col_A col_B col_C
1 bar 20.0 0.2
999 NaN NaN NaN

将来的にはエラーになるよという旨のFutureWarningが出ます。また、存在しないindexの分はNaNが要素に入ります。

# pandas 0.25.3
df1.loc[:, ['col_A', 'col-C']] # 列名を間違えた場合

---

col_A col-C
0 foo NaN
1 bar NaN
2 baz NaN

列を指定するときにうっかり列名を間違えたりすると、全要素がNaNの列が新たに作られたDataFrameが返ってきます。
実際のデータ分析ではDataFrameを表示しないでしょうから、分析を進めるとしばらく後で思わぬエラーに遭遇して戸惑うかもしれません。

この挙動は1.0以降で変更されました。

# pandas 1.0.0

df1.loc[[1, 999]]

---

KeyError: 'Passing list-likes to .loc or [] with any missing labels is no longer supported, see https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#deprecate-loc-reindex-listlike'

1.0以降では存在しない行名が含まれている場合、KeyErrorが上がります。
リリースノートはこちらです。
(「列名を間違えた場合」の例でも全く同じエラーになるので、こちらは割愛します)

# pandas 1.3.3 = 最新版

df1.loc[[1, 999]]

---

KeyError: '[999] not in index'

さらに1.1以降では、エラーメッセージが分かりやすくなりました。どのラベルが存在しないかを表示してくれます1
リリースノートはこちらです。

まとめ

1.0.0 メジャーアップデート後のpandasの、新しい機能や変更点を紹介しました。Experimental Featureもあり、使う際には注意も必要ですが、必要に応じて活用していけると良いですね。


  1. 1.調べていたら1.2.3と最新1.3.3との間でKeyErrorのメッセージが少し違っていることにも気づいたのですが、あまりにもマニアックなので脚注に書くだけに留めておきます。いつ変更されたんだろう?