note.nkmk.me

pandasで条件に応じて値を代入(where, mask)

Date: 2018-05-10 / tags: Python, pandas

pandasで条件に応じて値を代入する方法を説明する。if文を使うわけではないが、if then ...あるいはif then ... else ...的な条件分岐の処理が可能。

特定の値の置換、欠損値NaNの置換や削除については以下の記事を参照。

以下のpandas.DataFrameを例とする。

import pandas as pd
import numpy as np

df = pd.DataFrame({'A': [-20, -10, 0, 10, 20],
                   'B': [1, 2, 3, 4, 5],
                   'C': ['a', 'b', 'b', 'b', 'a']})

print(df)
#     A  B  C
# 0 -20  1  a
# 1 -10  2  b
# 2   0  3  b
# 3  10  4  b
# 4  20  5  a

以下の内容について説明する。

  • loc, ilocでブールインデックス参照
  • pandas.DataFrame, Serieswhere()メソッド
  • pandas.DataFrame, Seriesmask()メソッド
  • NumPyのwhere()関数
スポンサーリンク

loc, ilocでブールインデックス参照

以下のような書き方で条件に応じてスカラー値を代入することができる。

df.loc[df['A'] < 0, 'A'] = -100
df.loc[~(df['A'] < 0), 'A'] = 100
print(df)
#      A  B  C
# 0 -100  1  a
# 1 -100  2  b
# 2  100  3  b
# 3  100  4  b
# 4  100  5  a

順を追って説明する。

pandas.DataFrameあるいはpandas.DataFrameの列(= pandas.Series)に対して比較演算を行うと、bool型のpandas.DataFrameあるいはpandas.Seriesが得られる。

例はpandas.DataFrameの列(= pandas.Series)に対する処理。~は否定演算子。

print(df['A'] < 0)
# 0     True
# 1     True
# 2    False
# 3    False
# 4    False
# Name: A, dtype: bool

print(~(df['A'] < 0))
# 0    False
# 1    False
# 2     True
# 3     True
# 4     True
# Name: A, dtype: bool

bool型のpandas.Serieslocまたはilocの行指定に使うと、Trueの行のみが選択される。locは行名・列名での指定で、ilocは行番号・列番号での指定。

print(df.loc[df['A'] < 0, 'A'])
# 0   -100
# 1   -100
# Name: A, dtype: int64

loc, ilocでの参照は値の取得だけでなく代入にも使える。bool型のpandas.SeriesTrueの行(条件を満たす行)、指定した列の要素が右辺のスカラー値に変更される。

df.loc[df['A'] < 0, 'A'] = -10
print(df)
#      A  B  C
# 0  -10  1  a
# 1  -10  2  b
# 2  100  3  b
# 3  100  4  b
# 4  100  5  a

スカラー値でなく、pandas.Seriesやリスト・配列を指定することも可能。対応する行の値が代入される。

df.loc[~(df['A'] < 0), 'A'] = df['B']
print(df)
#     A  B  C
# 0 -10  1  a
# 1 -10  2  b
# 2   3  3  b
# 3   4  4  b
# 4   5  5  a

ここまでの例では既存の列の要素に代入したが、新しい列名を指定すると新しい列が追加され、条件を満たす行に値を代入できる。

df.loc[df['B'] % 2 == 0, 'D'] = 'even'
df.loc[df['B'] % 2 != 0, 'D'] = 'odd'
print(df)
#     A  B  C     D
# 0 -10  1  a   odd
# 1 -10  2  b  even
# 2   3  3  b   odd
# 3   4  4  b  even
# 4   5  5  a   odd

複数条件をand, orで指定することも可能。&, |を使い、条件ごとに括弧で囲む。

新たな列を追加する場合、条件を満たさない要素は欠損値NaNとなる。NaNを含む列の型dtypefloatになるので注意。

df.loc[~(df['A'] < 0) & (df['C'] == 'b'), 'E'] = df['B'] * 2
print(df)
#     A  B  C     D    E
# 0 -10  1  a   odd  NaN
# 1 -10  2  b  even  NaN
# 2   3  3  b   odd  6.0
# 3   4  4  b  even  8.0
# 4   5  5  a   odd  NaN

ある列の値に応じて二つの列のいずれかを選択するような処理は以下のように書ける。

df.loc[~(df['A'] < 0), 'A'] = 10
print(df)
#     A  B  C     D    E
# 0 -10  1  a   odd  NaN
# 1 -10  2  b  even  NaN
# 2  10  3  b   odd  6.0
# 3  10  4  b  even  8.0
# 4  10  5  a   odd  NaN

df.loc[df['C'] == 'a', 'F'] = df['A']
df.loc[df['C'] == 'b', 'F'] = df['B']
print(df)
#     A  B  C     D    E     F
# 0 -10  1  a   odd  NaN -10.0
# 1 -10  2  b  even  NaN   2.0
# 2  10  3  b   odd  6.0   3.0
# 3  10  4  b  even  8.0   4.0
# 4  10  5  a   odd  NaN  10.0

最初の代入ではNaNが存在するためfloat型になっている。astype()メソッドで型dtypeを変更できる。

df['F'] = df['F'].astype(int)
print(df)
#     A  B  C     D    E   F
# 0 -10  1  a   odd  NaN -10
# 1 -10  2  b  even  NaN   2
# 2  10  3  b   odd  6.0   3
# 3  10  4  b  even  8.0   4
# 4  10  5  a   odd  NaN  10

locilocではリストで複数の列を指定することも可能。

df.loc[df['C'] == 'a', ['E', 'F']] = 100
print(df)
#     A  B  C     D      E    F
# 0 -10  1  a   odd  100.0  100
# 1 -10  2  b  even    NaN    2
# 2  10  3  b   odd    6.0    3
# 3  10  4  b  even    8.0    4
# 4  10  5  a   odd  100.0  100

pandas.DataFrameに対して比較演算するとbool型のpandas.DataFrameが得られるが、ブロードキャストできないため、これまでの例のように値を代入するとエラーとなる。

print(df < 0)
#        A      B     C     D      E      F
# 0   True  False  True  True  False  False
# 1   True  False  True  True  False  False
# 2  False  False  True  True  False  False
# 3  False  False  True  True  False  False
# 4  False  False  True  True  False  False

print(df[df < 0])
#       A   B  C     D   E   F
# 0 -10.0 NaN  a   odd NaN NaN
# 1 -10.0 NaN  b  even NaN NaN
# 2   NaN NaN  b   odd NaN NaN
# 3   NaN NaN  b  even NaN NaN
# 4   NaN NaN  a   odd NaN NaN

# df[df < 0] = 0
# TypeError: Cannot do inplace boolean setting on mixed-types with a non np.nan value

pandas.DataFrame全体に条件を適用したい場合は次に説明するwhere()メソッドかmask()メソッドを使う。

pandas.DataFrame, Seriesのwhereメソッド

pandas.DataFrame, pandas.Seriesのメソッドにwhere()がある。

第一引数にbool値の要素をもつpandas.Seriesや配列を指定すると、Trueの要素の値は呼び出し元のオブジェクトのままで、Falseの要素の値がNaNとなる。

df = pd.DataFrame({'A': [-20, -10, 0, 10, 20],
                   'B': [1, 2, 3, 4, 5],
                   'C': ['a', 'b', 'b', 'b', 'a']})
print(df)
#     A  B  C
# 0 -20  1  a
# 1 -10  2  b
# 2   0  3  b
# 3  10  4  b
# 4  20  5  a

print(df['A'].where(df['C'] == 'a'))
# 0   -20.0
# 1     NaN
# 2     NaN
# 3     NaN
# 4    20.0
# Name: A, dtype: float64

第二引数にスカラー値やpandas.Series、配列を指定すると、Falseの要素の値としてNaNの代わりにその値が使われる。NumPyのwhere()関数とは違ってTrueの値は指定でないため、Trueの要素の値にスカラー値を代入することはできない。

print(df['A'].where(df['C'] == 'a', 100))
# 0    -20
# 1    100
# 2    100
# 3    100
# 4     20
# Name: A, dtype: int64

print(df['A'].where(df['C'] == 'a', df['B']))
# 0   -20
# 1     2
# 2     3
# 3     4
# 4    20
# Name: A, dtype: int64

新たな列として追加することも可能。

df['D'] = df['A'].where(df['C'] == 'a', df['B'])
print(df)
#     A  B  C   D
# 0 -20  1  a -20
# 1 -10  2  b   2
# 2   0  3  b   3
# 3  10  4  b   4
# 4  20  5  a  20

引数inplace=Trueとすると元のオブジェクトが変更される。

df['D'].where((df['D'] % 2 == 0) & (df['A'] < 0), df['D'] * 100, inplace=True)
print(df)
#     A  B  C     D
# 0 -20  1  a   -20
# 1 -10  2  b     2
# 2   0  3  b   300
# 3  10  4  b   400
# 4  20  5  a  2000

pandas.DataFrameにもwhere()メソッドが用意されている。第一引数に呼び出し元と同じサイズのbool値の要素をもつpandas.DataFrameや二次元配列を条件として指定する。

print(df < 0)
#        A      B     C      D
# 0   True  False  True   True
# 1   True  False  True  False
# 2  False  False  True  False
# 3  False  False  True  False
# 4  False  False  True  False

print(df.where(df < 0))
#       A   B  C     D
# 0 -20.0 NaN  a -20.0
# 1 -10.0 NaN  b   NaN
# 2   NaN NaN  b   NaN
# 3   NaN NaN  b   NaN
# 4   NaN NaN  a   NaN

print(df.where(df < 0, df * 2))
#     A   B  C     D
# 0 -20   2  a   -20
# 1 -10   4  b     4
# 2   0   6  b   600
# 3  20   8  b   800
# 4  40  10  a  4000

print(df.where(df < 0, 100))
#      A    B  C    D
# 0  -20  100  a  -20
# 1  -10  100  b  100
# 2  100  100  b  100
# 3  100  100  b  100
# 4  100  100  a  100

pandas.DataFrame, Seriesのmaskメソッド

pandas.DataFrame, pandas.Seriesのメソッドにmask()がある。

mask()メソッドはwhere()メソッドの逆で、第一引数に指定した条件がFalseの要素が呼び出し元のオブジェクトのままで、Trueの要素がNaNまたは第二引数で指定した値となる。

それ以外の使い方はwhere()と同じ。引数inplaceもある。

df = pd.DataFrame({'A': [-20, -10, 0, 10, 20],
                   'B': [1, 2, 3, 4, 5],
                   'C': ['a', 'b', 'b', 'b', 'a']})
print(df)
#     A  B  C
# 0 -20  1  a
# 1 -10  2  b
# 2   0  3  b
# 3  10  4  b
# 4  20  5  a

print(df['C'].mask(df['C'] == 'a'))
# 0    NaN
# 1      b
# 2      b
# 3      b
# 4    NaN
# Name: C, dtype: object

print(df['C'].mask(df['C'] == 'a', 100))
# 0    100
# 1      b
# 2      b
# 3      b
# 4    100
# Name: C, dtype: object

df['D'] = df['A'].mask(df['C'] == 'a', df['B'])
print(df)
#     A  B  C   D
# 0 -20  1  a   1
# 1 -10  2  b -10
# 2   0  3  b   0
# 3  10  4  b  10
# 4  20  5  a   5

df['D'].mask(df['D'] % 2 != 0, df['D'] * 10, inplace=True)
print(df)
#     A  B  C   D
# 0 -20  1  a  10
# 1 -10  2  b -10
# 2   0  3  b   0
# 3  10  4  b  10
# 4  20  5  a  50

第一引数の条件を満たす(Trueとなる)要素に第二引数が代入されるのでwhere()より直感的な気がする。

pandas.DataFrameにもmask()メソッドが用意されている。

print(df.mask(df < 0, -100))
#      A  B     C    D
# 0 -100  1  -100   10
# 1 -100  2  -100 -100
# 2    0  3  -100    0
# 3   10  4  -100   10
# 4   20  5  -100   50

この例のように数値と文字列が混在しているオブジェクトに対して、数値の列のみにメソッドを適用したい場合は、select_dtypes()を使って以下のようにできる。

print(df.select_dtypes(include='number').mask(df < 0, -100))
#      A  B    D
# 0 -100  1   10
# 1 -100  2 -100
# 2    0  3    0
# 3   10  4   10
# 4   20  5   50

数値の列のみを処理したあとで数値以外の列を連結することも可能。

df_mask = df.select_dtypes(include='number').mask(df < 0, -100)
df_mask = pd.concat([df_mask, df.select_dtypes(exclude='number')], axis=1)
print(df_mask.sort_index(axis=1))
#      A  B  C    D
# 0 -100  1  a   10
# 1 -100  2  b -100
# 2    0  3  b    0
# 3   10  4  b   10
# 4   20  5  a   50

NumPyのwhere関数

NumPyのwhere()関数を利用することでも条件に応じて値を代入できる。

pandasのwhere()メソッドまたはmask()メソッドでは、第二引数で指定できるのはFalse, Trueのいずれかの場合に代入される値のみで、もう一方は呼び出し元のオブジェクトの値がそのまま使われる。このため、条件によってスカラー値を選択するような処理はできない。

NumPyのwhere()関数では第一引数に条件、第二引数に条件がTrueの要素に代入される値、第三引数に条件がFalseの要素に代入される値を指定できる。第二、第三引数にはスカラー値も配列も指定可能でブロードキャストして代入される。

numpy.where()が返すのはNumPy配列ndarray

pandas.DataFrameの列としては一次元のnumpy.ndarrayをそのまま指定できる。

df = pd.DataFrame({'A': [-20, -10, 0, 10, 20],
                   'B': [1, 2, 3, 4, 5],
                   'C': ['a', 'b', 'b', 'b', 'a']})
print(df)
#     A  B  C
# 0 -20  1  a
# 1 -10  2  b
# 2   0  3  b
# 3  10  4  b
# 4  20  5  a

print(np.where(df['B'] % 2 == 0, 'even', 'odd'))
# ['odd' 'even' 'odd' 'even' 'odd']

print(np.where(df['C'] == 'a', df['A'], df['B']))
# [-20   2   3   4  20]

df['D'] = np.where(df['B'] % 2 == 0, 'even', 'odd')
print(df)
#     A  B  C     D
# 0 -20  1  a   odd
# 1 -10  2  b  even
# 2   0  3  b   odd
# 3  10  4  b  even
# 4  20  5  a   odd

df['E'] = np.where(df['C'] == 'a', df['A'], df['B'])
print(df)
#     A  B  C     D   E
# 0 -20  1  a   odd -20
# 1 -10  2  b  even   2
# 2   0  3  b   odd   3
# 3  10  4  b  even   4
# 4  20  5  a   odd  20

条件にpandas.DataFrameを指定すると二次元のnumpy.ndarrayが返る。元のpandas.DataFrameindexcolumnsを使ってpandas.DataFrameを生成可能。

print(np.where(df < 0, df, 100))
# [[-20 100 'a' 'odd' -20]
#  [-10 100 'b' 'even' 100]
#  [100 100 'b' 'odd' 100]
#  [100 100 'b' 'even' 100]
#  [100 100 'a' 'odd' 100]]

df_np_where = pd.DataFrame(np.where(df < 0, df, 100),
                           index=df.index, columns=df.columns)

print(df_np_where)
#      A    B  C     D    E
# 0  -20  100  a   odd  -20
# 1  -10  100  b  even  100
# 2  100  100  b   odd  100
# 3  100  100  b  even  100
# 4  100  100  a   odd  100
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事