概要
こちらの続き。これで pandas
でのデータ選択についてはひとまず終わり。
Python pandas データ選択処理をちょっと詳しく <前編> - StatsFragments
Python pandas データ選択処理をちょっと詳しく <中編> - StatsFragments
サンプルデータの準備
データは 前編と同じものを使う。ただし変数名は変えた。
import pandas as pd s1 = pd.Series([1, 2, 3], index = ['I1', 'I2', 'I3']) df1 = pd.DataFrame({'C1': [11, 21, 31], 'C2': [12, 22, 32], 'C3': [13, 23, 33]}, index = ['I1', 'I2', 'I3']) s1 # I1 1 # I2 2 # I3 3 # dtype: int64 df1 # C1 C2 C3 # I1 11 12 13 # I2 21 22 23 # I3 31 32 33
where
でのデータ選択
これまでみたとおり、Series
から __getitem__
すると、条件に該当する要素のみが返ってくる。
s1[s1 > 2] # I3 3 # dtype: int64
が、ときには元データと同じ長さのデータがほしい場合がある。Series.where
を使うと 条件に該当しない ラベルは NaN
でパディングし、元データと同じ長さの結果を返してくれる。
where
の引数はデータと同じ長さの bool
型の numpy.array
もしくは Series
である必要がある。まあ Series
への論理演算では長さは変わらないので、以下のような使い方なら特別 意識する必要はないかも。
s1.where(s1 > 2) # I1 NaN # I2 NaN # I3 3 # dtype: float64
NaN
以外でパディングしたいんだよ、ってときは 第二引数に パディングに使う値を渡す。NaN
でなく 0 でパディングしたければ、
s1.where(s1 > 2, 0) # I1 0 # I2 0 # I3 3 # dtype: int64
また、第二引数にはデータと同じ長さの numpy.array
や Series
も渡せる。このとき、パディングはそれぞれ対応する位置にある値で行われ、第一引数の条件に該当しないデータを 第二引数で置換するような動きになる。つまり if - else
のような表現だと考えていい。
# 第一引数の条件に該当しない s1 の 1, 2番目の要素が array の 1, 2 番目の要素で置換される s1.where(s1 > 2, np.array([4, 5, 6])) # I1 4 # I2 5 # I3 3 # dtype: int64 # 置換用の Series を作る s2 = pd.Series([4, 5, 6], index = ['I1', 'I2', 'I3']) s2 # I1 4 # I2 5 # I3 6 # dtype: int64 # 第一引数の条件に該当しない s1 の 1, 2番目の要素が Series の 1, 2 番目の要素で置換される s1.where(s1 > 2, s2) # I1 4 # I2 5 # I3 3 # dtype: int64
DataFrame
でも同様。
df1.where(df1 > 22) # C1 C2 C3 # I1 NaN NaN NaN # I2 NaN NaN 23 # I3 31 32 33 # 0 でパディング df1.where(df1 > 22, 0) # C1 C2 C3 # I1 0 0 0 # I2 0 0 23 # I3 31 32 33 # 置換用の DataFrame を作る df2 = pd.DataFrame({'C1': [44, 54, 64], 'C2': [45, 55, 65], 'C3': [46, 56, 66]}, index = ['I1', 'I2', 'I3']) df2 # C1 C2 C3 # I1 44 45 46 # I2 54 55 56 # I3 64 65 66 # df1 のうち、22以下の値を df2 の値で置換 df1.where(df1 > 22, df2) # C1 C2 C3 # I1 44 45 46 # I2 54 55 23 # I3 31 32 33
where
がことさら便利なのは以下のようなケース。
DataFrame
に新しいカラムを作りたい。 (あるいは既存の列の値を置き換えたい)- 新しいカラムは、"C2" 列の値が 30を超える場合は "C1" 列の値を使う。
- それ以外は "C3" 列の値を使う。
これが一行でかける。
df1['C4'] = df1['C1'].where(df1['C2'] > 30, df1['C3']) df1 # C1 C2 C3 C4 # I1 11 12 13 13 # I2 21 22 23 23 # I3 31 32 33 31
mask
でのデータ選択
where
の逆の操作として mask
がある。こちらは第一引数の条件に該当するセルを NaN
でマスクする。ただし第二引数の指定はできないので 使いどころは限られる。
df1.mask(df1 > 22) # C1 C2 C3 C4 # I1 11 12 13 13 # I2 21 22 NaN NaN # I3 NaN NaN NaN NaN # NG! df1.mask(df1 > 22, 0) # TypeError: mask() takes exactly 2 arguments (3 given)
query
でのデータ選択
最後にquery
。query
で何か新しい処理ができるようになるわけではないが、__getitem__
と同じ操作がよりシンプル な表現で書ける。
numexpr
のインストール
query
を使うには numexpr
パッケージが必要なので、入っていなければインストールする。
pip install numexpr
サンプルデータの準備
また、さきほどの例で列追加したのでサンプルデータを作り直す。
df1 = pd.DataFrame({'C1': [11, 21, 31], 'C2': [12, 22, 32], 'C3': [13, 23, 33]}, index = ['I1', 'I2', 'I3']) df1 # C1 C2 C3 # I1 11 12 13 # I2 21 22 23 # I3 31 32 33
query
の利用
__getitem__
を利用したデータ選択では、論理演算の組み合わせで bool
の Series
を作ってやる必要がある。そのため、[]
内で元データへの参照 ( 下の例では df1
)が繰り返しでてくるし、複数条件の組み合わせの際は 演算順序の都合上 ()
がでてきたりと式が複雑になりがち。これはわかりにくい。
df1[df1['C1'] > 20] # C1 C2 C3 # I2 21 22 23 # I3 31 32 33 df1[df1['C2'] < 30] # C1 C2 C3 # I1 11 12 13 # I2 21 22 23 df1[(df1['C1'] > 20) & (df1['C2'] < 30)] # C1 C2 C3 # I2 21 22 23
同じ処理は query
を使うとすっきり書ける。query
の引数にはデータ選択に使う条件式を文字列で渡す。この式が評価される名前空間 = query
名前空間の中では、query
を呼び出したデータの列名が あたかも変数のように参照できる。
df1.query('C1 > 20 & C2 < 30') # C1 C2 C3 # I2 21 22 23
ただし、query
名前空間の中で使える表現は限られるので注意。例えば以下のような メソッド呼び出しはできない。
# NG! df1.query('C1.isin([11, 21])') # NotImplementedError: 'Call' nodes are not implemented
同じ処理を行う場合は in
演算子を使う。
# in を使えば OK df1.query('C1 in [11, 21]') # C1 C2 C3 # I1 11 12 13 # I2 21 22 23
また、numexpr
で利用できる関数 の呼び出しは query
名前空間上では使えないっぽい。
df1.query('C1 > sqrt(400)') # NotImplementedError: 'Call' nodes are not implemented
そのため、query
に渡す式表現では論理表現 以外を含めないほうがよい。条件によって式表現を変えたい、なんて場合は 式表現を都度 文字列として連結 + 生成するか、ローカル変数に計算結果を入れて式表現に渡す。
ローカル変数を式表現中に含める際は、変数名を @
ではじめる。
x = 20 # NG! df1.query('C1 > x') # UndefinedVariableError: name 'x' is not defined # OK! df1.query('C1 > @x') # C1 C2 C3 # I2 21 22 23 # I3 31 32 33
query
名前空間上で index
の値を参照する場合は、式表現中で index
と指定する。
df1.query('index in ["I1", "I2"]') # C1 C2 C3 # I1 11 12 13 # I2 21 22 23
index
が列名と重複した場合は 列名が優先。
df_idx = pd.DataFrame({'index': [1, 2, 3]}, index=[3, 2, 1]) df_idx # index # 3 1 # 2 2 # 1 3 df_idx.query('index >= 2') # index # 2 2 # 1 3
まとめ
where
を使うと 元データと同じ形のデータが取得できる。また、if - else
的表現が一行で書ける。- 複数の条件式を組み合わせる場合は
query
を使うとシンプルに書ける。
全三回で pandas
でのデータ選択処理をまとめた。公式ドキュメント、またAPIガイドには ここで記載しなかったメソッド / 例もあるので、より尖った使い方をしたい人は読んでみるといいですね。
ネタ募集 こういうのしたいんだけど?というネタがあればつづき書きます。