Pandas where
の使い方:データフレームの条件指定・置換をマスター
はじめに:データ操作における条件指定と置換の重要性
データ分析の世界では、データを読み込むだけでなく、目的に応じて整形し、加工することが不可欠です。特に、特定の条件を満たすデータに対して何らかの操作(例えば、値の変更や欠損値の補完)を行うことは、データ前処理の非常に一般的なタスクです。
Pandasは、Pythonで構造化データを効率的に扱うための強力なライブラリであり、これらのタスクを実行するための多様な機能を提供しています。その中でも、特定の条件に基づいてデータフレームの値を置き換える際に非常に役立つメソッドの一つに where
があります。
DataFrame.where()
メソッドは、指定した条件が False である要素を別の値で置き換えるために使用されます。これは一見すると直感に反するように思えるかもしれません。なぜなら、多くのデータ操作関数は「条件がTrueである要素」に対して操作を行うからです(例えば、ブールインデックスを使ったデータの抽出など)。しかし、この「条件がFalseの要素を置換する」という動作が、特定のユースケースにおいて非常に便利に機能するのです。
この記事では、Pandasの DataFrame.where()
メソッドに焦点を当て、その基本的な使い方から、other
引数による柔軟な置換、様々な条件の指定方法、そして他の関連メソッド(mask
、ブールインデックスなど)との比較までを詳細に解説します。この記事を通じて、where
メソッドを自在に使いこなし、Pandasでのデータ操作スキルをさらに向上させることを目指します。
DataFrame.where()
メソッドの基本
まず、DataFrame.where()
メソッドの基本的な使い方と、その動作原理について見ていきましょう。
where
メソッドは、以下の基本的なシグネチャを持ちます(完全なシグネチャはより多くの引数を含みますが、主要なものは以下の通りです)。
python
DataFrame.where(cond, other=nan, inplace=False, axis=None, level=None, errors='raise')
cond
: これが最も重要な引数です。置換を行うための条件を指定します。通常、真偽値(Boolean)のDataFrame、Series、またはNumPy配列を指定します。このcond
の形状は、操作対象のDataFrameと同じか、ブロードキャスト可能な形状である必要があります。other
:cond
で指定された条件が False である要素を置き換える値を指定します。デフォルトではnan
(Not a Number、欠損値)です。スカラー値、または操作対象のDataFrameと同じ形状のDataFrame/Series/NumPy配列を指定できます。inplace
: デフォルトはFalse
です。True
に設定すると、元のDataFrameが直接変更され、メソッドはNone
を返します。False
の場合、新しいDataFrameが返されます。axis
: 条件を評価する軸を指定します。通常はデフォルトのNone
のままで、要素ごとに条件が評価されます。level
: (MultiIndexの場合に使用)errors
: (比較的新しいバージョンで追加された引数で、非推奨または削除される可能性あり)
where
メソッドの最も重要なポイントは、その動作原理です。Pandasの where
は、指定された cond
に基づいて以下のように動作します。
cond
がTrue
の位置にある要素: 元のDataFrameのその位置にある値が そのまま保持されます。cond
がFalse
の位置にある要素:other
引数で指定された値に 置き換えられます。
つまり、where
は「条件を満たす(True)要素を残し、満たさない(False)要素を置き換える」メソッドであると言えます。これは、ブールインデックスを使って「条件を満たす要素を抽出する」のとは異なる動作であることに注意が必要です。
基本的な使い方:条件を満たさない要素をNaNで置換
最も基本的な使い方は、other
引数を指定しない場合です。この場合、条件を満たさない要素はデフォルト値である NaN
に置き換えられます。
簡単なデータフレームを作成して試してみましょう。
“`python
import pandas as pd
import numpy as np
サンプルデータフレームの作成
data = {‘A’: [10, 20, 30, 40, 50],
‘B’: [15, 25, 35, 45, 55],
‘C’: [22, 33, 11, 44, 0]}
df = pd.DataFrame(data)
print(“元のDataFrame:”)
print(df)
“`
元のDataFrame:
A B C
0 10 15 22
1 20 25 33
2 30 35 11
3 40 45 44
4 50 55 0
では、例えば「値が30より大きい要素のみを残し、それ以外をNaNで置き換える」という条件を適用してみましょう。条件 df > 30
を作成し、where
メソッドに渡します。
“`python
条件の作成: 各要素が30より大きいか?
condition = df > 30
print(“\n条件 (cond):”)
print(condition)
whereメソッドの適用 (otherを指定しない場合)
df_ जहां_default = df.where(condition)
print(“\nwhere適用後のDataFrame (other=NaN):”)
print(df_where_default)
“`
“`
条件 (cond):
A B C
0 False False False
1 False False True
2 False True False
3 True True True
4 True True False
where適用後のDataFrame (other=NaN):
A B C
0 NaN NaN NaN
1 NaN NaN 33.0
2 NaN 35.0 NaN
3 40.0 45.0 44.0
4 50.0 55.0 NaN
“`
結果を見てください。条件 df > 30
が True
である要素(例えば df.loc[3, 'A']
の 40)は元の値 40 のまま残っています。一方、条件が False
である要素(例えば df.loc[0, 'A']
の 10)はデフォルトの NaN
に置き換えられています。
NaN
が含まれることで、列のデータ型が整数型 (int64
) から浮動小数点型 (float64
) に自動的に変更されていることにも注意してください。これは、Pandasが欠損値を表現するために浮動小数点型の NaN
を使用する性質によるものです。
置換する値の指定 (other
引数)
where
メソッドの強力な機能の一つは、other
引数を使って、条件を満たさない要素を NaN
以外の特定の値で置き換えられる点です。other
には様々な種類のオブジェクトを指定できます。
スカラー値による置換
最も一般的な other
の指定方法は、単一のスカラー値(数値、文字列など)を指定することです。これにより、条件を満たさない全ての要素がそのスカラー値で置き換えられます。
前の例で、条件を満たさない要素を 0 に置き換えてみましょう。
“`python
whereメソッドの適用 (otherを指定)
df_where_scalar = df.where(df > 30, other=0)
print(“\nwhere適用後のDataFrame (other=0):”)
print(df_where_scalar)
“`
where適用後のDataFrame (other=0):
A B C
0 0 0 0
1 0 0 33
2 0 35 0
3 40 45 44
4 50 55 0
条件 df > 30
が True
の要素(40, 45, 44, 50, 55, 33)は元の値のままです。条件が False
の要素は全て 0 に置き換えられています。
other
に DataFrame, Series, または NumPy配列を指定する場合
other
引数には、スカラー値だけでなく、DataFrame、Series、またはNumPy配列を指定することも可能です。この場合、Pandasは cond
と other
の形状を考慮して、条件を満たさない要素を other
の対応する位置の値で置き換えます。
重要なのは、Pandasは cond
、元のDataFrame、そして other
の間でインデックスとカラム(列名)を基準にアライメント(位置合わせ)を行おうとする という点です。
-
other
が元のDataFrameと全く同じ形状・インデックス・カラムを持つ場合:
cond
がFalse
の位置にある要素は、other
の全く同じ位置にある値で置き換えられます。これは、要素ごとのカスタム置換を行う場合に便利です。“`python
元のDataFrameと同じ形状のother DataFrameを作成
other_data = {‘A’: [-10, -20, -30, -40, -50],
‘B’: [-15, -25, -35, -45, -55],
‘C’: [-22, -33, -11, -44, -0]}
df_other = pd.DataFrame(other_data)print(“\nOther DataFrame (df_other):”)
print(df_other)whereメソッドの適用 (otherにDataFrameを指定)
df_where_df_other = df.where(df > 30, other=df_other)
print(“\nwhere適用後のDataFrame (other=df_other):”)
print(df_where_df_other)
“`“`
Other DataFrame (df_other):
A B C
0 -10 -15 -22
1 -20 -25 -33
2 -30 -35 -11
3 -40 -45 -44
4 -50 -55 0where適用後のDataFrame (other=df_other):
A B C
0 -10 -15 -22
1 -20 -25 33
2 -30 35 -11
3 40 45 44
4 50 55 0
``
df.loc[0, ‘A’]
結果を見ると、例えば(値は 10) は
df > 30の条件が
Falseです。この位置は
df_other.loc[0, ‘A’]の値 -10 で置き換えられています。一方、
df.loc[3, ‘A’](値は 40) は条件が
True` なので、元の値 40 のままです。 -
other
が元のDataFrameとインデックスまたはカラムが異なる場合:
Pandasはインデックスとカラムを基準にアライメントを試みます。一致しない位置の要素はNaN
になります。“`python
インデックスが異なるother DataFrameを作成
other_data_shifted_index = {‘A’: [-10, -20, -30],
‘B’: [-15, -25, -35]}
df_other_shifted_index = pd.DataFrame(other_data_shifted_index, index=[1, 2, 3])print(“\nOther DataFrame (shifted index):”)
print(df_other_shifted_index)whereメソッドの適用 (otherにインデックスが異なるDataFrameを指定)
df_where_shifted_index = df.where(df > 30, other=df_other_shifted_index)
print(“\nwhere適用後のDataFrame (other=df_other_shifted_index):”)
print(df_where_shifted_index)
“`“`
Other DataFrame (shifted index):
A B
1 -10 -15
2 -20 -25
3 -30 -35where適用後のDataFrame (other=df_other_shifted_index):
A B C
0 NaN NaN NaN
1 -10.0 -15.0 33.0
2 -20.0 35.0 NaN
3 40.0 45.0 44.0
4 NaN NaN NaN
``
df_other_shifted_index
元のDataFrameはインデックス 0, 1, 2, 3, 4、カラム A, B, C です。はインデックス 1, 2, 3、カラム A, B です。
whereの結果を見ると:
True
* 条件がの要素は、対応するインデックス/カラムが存在すれば保持されます(例:
df.loc[3, ‘C’]は 44 のまま)。
False
* 条件がの要素は、
otherの対応するインデックス/カラムの値で置き換えられます。しかし、
otherに存在しないインデックス/カラムの位置は
NaNになります(例:
df.loc[0, ‘A’]は条件が
Falseですが、
df_other_shifted_indexにインデックス 0 が無いため
NaNになります)。
other
* 元のDataFrameに存在しないカラム 'C' は、にも存在しないため、条件に関わらず全て
NaN` になります。このアライメントの挙動は、Pandasの他の多くの操作(算術演算など)と同様です。
other
に Series を指定した場合も同様に、SeriesのインデックスとDataFrameのカラムまたはインデックス(axis
引数による)がアライメントされます。 -
other
がブロードキャスト可能な形状を持つ場合:
NumPy配列やSeriesを指定した場合、PandasはNumPyのブロードキャスティングルールに従ってother
を拡張しようとします。例えば、単一のSeriesをother
に指定した場合、そのSeriesはDataFrameの各行(または各列、axis
による)にブロードキャストされる可能性があります。“`python
カラム’A’に対応するSeriesを作成
other_series_A = pd.Series([-10, -20, -30, -40, -50], index=df.index)
print(“\nOther Series (other_series_A):”)
print(other_series_A)whereメソッドの適用 (otherにSeriesを指定)
通常、otherにSeriesを指定すると、DataFrameのカラムとSeriesのインデックスでアライメントされる
df_where_series = df.where(df > 30, other=other_series_A, axis=’columns’) # axis=’columns’が重要
print(“\nwhere適用後のDataFrame (other=other_series_A, axis=’columns’):”)
print(df_where_series)
Other Series (other_series_A):
0 -10
1 -20
2 -30
3 -40
4 -50
dtype: int64where適用後のDataFrame (other=other_series_A, axis=’columns’):
A B C
0 -10.0 15.0 22.0
1 -20.0 25.0 33.0
2 -30.0 35.0 11.0
3 40.0 45.0 44.0
4 50.0 55.0 0.0
``
other
この例では、にインデックスが元のDataFrameのインデックスと一致するSeriesを指定し、
axis=’columns’` を指定しています。これにより、Seriesは各行に対してブロードキャストされ、DataFrameの各カラムとSeriesのインデックスがアライメントされます。しかし、Seriesのインデックスは ‘0’ から ‘4’ であり、DataFrameのカラムは ‘A’, ‘B’, ‘C’ です。これらは一致しないため、通常はこの方法では期待する結果(例えば、’A’列の条件Falseを対応するother_series_Aの値で置き換える)は得られません。other
にSeriesを指定して期待通りの結果を得るには、SeriesのインデックスがDataFrameのカラム名と一致するか、またはSeriesのインデックスがDataFrameのインデックス名と一致し、かつaxis='index'
またはaxis=0
を指定する必要があります。DataFrameの各カラムに対して、対応する値をSeriesで置き換える(Seriesのインデックスがカラム名である必要あり):
“`pythonカラム名に対応するSeriesを作成
other_series_by_column = pd.Series({‘A’: -100, ‘B’: -200, ‘C’: -300})
print(“\nOther Series (by column name):”)
print(other_series_by_column)whereメソッドの適用 (otherにカラム名Seriesを指定, axisはデフォルトでOK)
df_where_series_column = df.where(df > 30, other=other_series_by_column)
print(“\nwhere適用後のDataFrame (other=other_series_by_column):”)
print(df_where_series_column)
Other Series (by column name):
A -100
B -200
C -300
dtype: int64where適用後のDataFrame (other=other_series_by_column):
A B C
0 -100 -200 -300
1 -100 -200 33
2 -100 35 -300
3 40 45 44
4 50 55 -300
``
other_series_by_column
この例では、のインデックス('A', 'B', 'C')がDataFrameのカラム名と一致しています。
whereはこれを認識し、条件がFalseである要素を、その要素が含まれるカラムに対応するSeriesの値('A'カラムなら -100、'B'カラムなら -200など)で置き換えています。例えば、
df.loc[0, ‘A’]` は条件Falseなので、’A’カラムに対応する -100 で置き換えられています。DataFrameの各行に対して、対応する値をSeriesで置き換える(Seriesのインデックスが行インデックスである必要あり):
“`python行インデックスに対応するSeriesを作成
other_series_by_index = pd.Series({0: 99, 1: 88, 2: 77, 3: 66, 4: 55})
print(“\nOther Series (by index):”)
print(other_series_by_index)whereメソッドの適用 (otherに行インデックスSeriesを指定, axis=’index’)
df_where_series_index = df.where(df > 30, other=other_series_by_index, axis=’index’) # axis=’index’が重要
print(“\nwhere適用後のDataFrame (other=other_series_by_index, axis=’index’):”)
print(df_where_series_index)
Other Series (by index):
0 99
1 88
2 77
3 66
4 55
dtype: int64where適用後のDataFrame (other=other_series_by_index, axis=’index’):
A B C
0 99 99 99
1 88 88 33
2 77 35 77
3 40 45 44
4 50 55 55
``
other_series_by_index
この例では、のインデックス(0, 1, 2, 3, 4)がDataFrameの行インデックスと一致しています。
axis=’index’を指定することで、
whereはSeriesを各カラムに対してブロードキャストし、条件がFalseである要素を、その要素が含まれる行に対応するSeriesの値で置き換えています。例えば、
df.loc[0, ‘A’]は条件Falseなので、インデックス 0 に対応する 99 で置き換えられています。
df.loc[0, ‘B’]や
df.loc[0, ‘C’]` も同様に 99 で置き換えられています。このように、
other
に Series や DataFrame を指定する場合は、アライメントの挙動とaxis
引数の使い方を理解することが重要です。
条件の作成方法
where
メソッドに渡す cond
(条件) は、真偽値を持つDataFrame、Series、またはNumPy配列であれば、様々な方法で作成できます。
比較演算子 (>
, <
, ==
, !=
, >=
, <=
)
これは最も一般的で基本的な条件作成方法です。DataFrameやSeriesに対してスカラー値や他のDataFrame/Seriesを使って直接比較演算を行うと、結果として真偽値のDataFrame/Seriesが得られます。
“`python
例: ‘A’列の値が30未満
condition_A_less_than_30 = df[‘A’] < 30
print(“\n条件 (‘A’ < 30):”)
print(condition_A_less_than_30)
df_where_A_cond = df.where(condition_A_less_than_30, other=999)
print(“\nwhere適用後のDataFrame (‘A’ < 30):”)
print(df_where_A_cond)
``
df[‘A’] < 30`) です。このSeriesはDataFrameの各行に対してブロードキャストされ、各列の要素がその行の条件(’A’列の値が30未満かどうか)で評価されます。しかし、これは通常やりたいことではありません。特定の列の条件を使ってDataFrame全体をフィルタリングしたい場合は、条件の形状に注意が必要です。
この例では、条件が単一のSeries (
DataFrame全体に対する条件:
DataFrame全体と比較演算子を使うと、要素ごとの真偽値DataFrameが生成されます。これが where
の cond
として最もよく使われる形式です。
“`python
例: DataFrame全体の要素が20より大きい
condition_df_greater_than_20 = df > 20
print(“\n条件 (df > 20):”)
print(condition_df_greater_than_20)
df_where_df_cond = df.where(condition_df_greater_than_20, other=-1)
print(“\nwhere適用後のDataFrame (df > 20):”)
print(df_where_df_cond)
``
df > 20
この結果は、最初に見た基本的な使い方の応用です。条件が
Trueである要素は保持され、
False` である要素は -1 に置き換えられます。
特定の列の条件を使って、DataFrame全体をフィルタリングしたい場合:
例えば、「’A’列の値が30未満であるような行」の全ての列の値を変更したい場合。これは通常、ブールインデックスで行うことが多いですが、where
を使う場合は、条件のSeriesをDataFrameの形状に「拡張」する必要があります。これは、Pandasがブロードキャストを試みることで自動的に行われる場合と、そうでない場合があります。最も確実な方法は、条件のSeriesをDataFrameの形状に合うように操作することです。しかし、where
の引数としてSeriesを渡すと、PandasはSeriesのインデックスとDataFrameのカラムをアライメントしようとする(または行にブロードキャストしようとする)ため、意図した結果にならないことが多いです。
特定の列の条件を使ってDataFrame全体を操作したい場合は、通常は以下のように条件DataFrameを明示的に作成するか、loc
とブールインデックスを組み合わせる方が直感的です。しかし、あえて where
で行う場合は、条件のSeriesをDataFrame全体に適用する挙動を理解する必要があります。
“`python
‘A’列の条件 (Series)
condition_A_less_than_30 = df[‘A’] < 30
この条件SeriesをDataFrameの形状に拡張してwhereに渡す
これはPandasが内部的に行おうとするブロードキャストの例だが、whereでは期待通りにならないことが多い
正攻法ではないが、whereの挙動理解のために試す
df_where_A_col_cond = df.where(condition_A_less_than_30, other=999, axis=’index’) # axis=’index’で行方向にブロードキャスト
print(“\nwhere適用後のDataFrame (‘A’ < 30, axis=’index’):”)
print(df_where_A_col_cond)
``
axis=’index’を指定すると、条件Seriesは各カラムにブロードキャストされます。つまり、DataFrameの各要素
df.loc[i, j]は、その行インデックス
iに対応する条件
condition_A_less_than_30[i]で評価されます。
condition_A_less_than_30[0]
* 行 0:は
True(
10 < 30)。行0の全要素はそのまま。
condition_A_less_than_30[1]
* 行 1:は
True(
20 < 30)。行1の全要素はそのまま。
condition_A_less_than_30[2]
* 行 2:は
False(
30 < 30は False)。行2の全要素は other=999 で置換。
condition_A_less_than_30[3]
* 行 3:は
False(
40 < 30は False)。行3の全要素は other=999 で置換。
condition_A_less_than_30[4]
* 行 4:は
False(
50 < 30` は False)。行4の全要素は other=999 で置換。
この結果は、「’A’列の値が30未満 でない 行」を 999 で埋めることになります。このように、条件がDataFrame全体の形状と一致しない場合は、axis
やブロードキャストの挙動を注意深く理解する必要があります。通常、特定の列の条件でDataFrame全体を操作したい場合は、ブールインデックスと loc
を使う方が意図が明確になります。
論理演算子 (&
, |
, ~
)
複数の条件を組み合わせるには、論理積 (&
)、論理和 (|
)、論理否定 (~
) を使用します。これらの演算子は、要素ごとの論理演算を行います。
“`python
条件1: ‘A’列が20より大きい
cond1 = df[‘A’] > 20
条件2: ‘B’列が40未満
cond2 = df[‘B’] < 40
条件3: ‘C’列が偶数
cond3 = df[‘C’] % 2 == 0
複合条件: (‘A’ > 20 AND ‘B’ < 40) OR (‘C’ が偶数)
注意: 論理演算子を使う場合は、各条件を括弧で囲む必要があります (演算子の優先順位のため)
compound_condition = ((df[‘A’] > 20) & (df[‘B’] < 40)) | (df[‘C’] % 2 == 0)
print(“\n複合条件:”)
print(compound_condition)
where適用
df_where_compound = df.where(compound_condition, other=-99)
print(“\nwhere適用後のDataFrame (複合条件):”)
print(df_where_compound)
``
where
ここでも、複合条件は単一の真偽値Seriesとして生成されています。このSeriesをDataFrameのに渡すと、デフォルトではDataFrameのカラムとSeriesのインデックス(行インデックス)をアライメントしようとします。つまり、Seriesのインデックス 0 は DataFrameのカラム名 '0' と一致するか?といった評価が行われ、通常は一致しないため、全ての列に対して条件が
Falseと評価された位置は
NaNになります(
other指定があれば
other` の値)。
特定の行に対する複合条件として使いたい場合は、やはり axis='index'
を指定して行方向のブロードキャストを意図的に行う必要があります。
“`python
複合条件 (Series) を axis=’index’ で適用
df_where_compound_axis = df.where(compound_condition, other=-99, axis=’index’)
print(“\nwhere適用後のDataFrame (複合条件, axis=’index’):”)
print(df_where_compound_axis)
“`
この結果は、「複合条件を満たさない行」の全要素が -99 に置き換えられたものになります。
isin()
メソッド
特定の複数の値のいずれかに一致するかどうかを条件とする場合は、isin()
メソッドが便利です。
“`python
‘C’列の値が 11 または 44 または 0 であるか?
condition_C_isin = df[‘C’].isin([11, 44, 0])
print(“\n条件 (‘C’ isin [11, 44, 0]):”)
print(condition_C_isin)
where適用 (他の列は変更せず、’C’列だけ条件適用したい場合)
この場合は、他の列の条件をTrueにしておき、’C’列の条件を組み合わせる必要がある
condition_all_true = pd.DataFrame(True, index=df.index, columns=df.columns)
condition_only_C = condition_all_true.copy()
condition_only_C[‘C’] = condition_C_isin # ‘C’列だけ条件isinを適用
print(“\n条件 (only ‘C’ isin [11, 44, 0]):”)
print(condition_only_C)
df_where_C_isin = df.where(condition_only_C, other=-500)
print(“\nwhere適用後のDataFrame (only ‘C’ isin [11, 44, 0]):”)
print(df_where_C_isin)
``
condition_only_C
ここでは、'C'列のみに条件を適用し、それ以外の列は条件を満たす(=元の値を保持する)ようにするために、条件DataFrameを作成しています。このDataFrameは、'C'列以外は全て
True、'C'列は
isinの結果という真偽値になります。
whereはこのDataFrame全体を
cond` として評価します。
notna()
/ isna()
メソッド
欠損値 (NaN
, NaT
など) であるかどうかの条件を作成するには、isna()
または notna()
メソッドを使用します。これは特に、欠損値を特定のルールで補完する場合に where
と組み合わせてよく使われます。
“`python
データフレームに欠損値を導入
df_with_nan = df.copy()
df_with_nan.loc[1, ‘B’] = np.nan
df_with_nan.loc[3, ‘A’] = np.nan
df_with_nan.loc[4, ‘C’] = np.nan
print(“\n欠損値を含むDataFrame:”)
print(df_with_nan)
条件: 欠損値であるか?
condition_is_na = df_with_nan.isna()
print(“\n条件 (isna):”)
print(condition_is_na)
where適用: 欠損値でないものを残し、欠損値であるものを0で置換
つまり、欠損値を0で補完する (fillnaと同じような結果)
df_where_fillna_0 = df_with_nan.where(df_with_nan.notna(), other=0) # notna()はisna()の逆
print(“\nwhere適用後のDataFrame (欠損値を0で置換):”)
print(df_where_fillna_0)
``
df_with_nan.notna()
この例では、を条件として使用しています。これは「欠損値 **でない**」場合に
Trueとなります。
whereは条件が
Trueの要素(つまり欠損値でない元の値)を保持し、条件が
Falseの要素(つまり欠損値)を
other=0で置き換えます。これは、欠損値を0で補完する
fillna(0)` と同じ結果になります。
where
メソッドの応用例
where
メソッドは、様々なデータ操作タスクに応用できます。
1. 欠損値の補完
前の例のように、notna()
または isna()
と where
を組み合わせて欠損値を補完できます。other
にスカラー値だけでなく、他の列の値や計算結果を指定することも可能です。
例えば、「’A’列の欠損値を、同じ行の’B’列の値で補完する」という操作を考えます。
“`python
欠損値を含むDataFrame (前の例と同じ)
df_with_nan
条件: ‘A’列が欠損値であるか? (Series)
cond_A_is_na = df_with_nan[‘A’].isna()
otherの値: ‘B’列の値 (Series)
other_B_col = df_with_nan[‘B’]
where適用: ‘A’列の欠損値を’B’列の値で補完
注意: condがSeries、otherがSeriesの場合、
whereは通常、DataFrameのカラムとSeriesのインデックスをアライメントしようとする。
特定の列だけ操作したい場合は、condとotherの形状を調整するか、
locを使う方が直感的だが、whereで行う方法を考える。
ここでは、A列だけにwhereを適用し、他の列はそのままにする方法を考える。
方法1: A列だけ取り出してwhereを適用し、元のDataFrameに戻す
df_with_nan[‘A’] = df_with_nan[‘A’].where(df_with_nan[‘A’].notna(), other=df_with_nan[‘B’])
print(“\nwhere適用後のDataFrame (‘A’列欠損値を’B’列で補完):”)
print(df_with_nan)
``
where
この方法では、まず対象のカラム('A'列)をSeriesとして取り出し、そのSeriesに対してメソッドを適用しています。Seriesの
whereメソッドは、DataFrameの
whereと同様に動作しますが、対象がSeriesである点が異なります。
condも
other` もSeries(またはスカラー)で指定でき、これらはSeriesのインデックスでアライメントされます。
df_with_nan['A'].notna()
は ‘A’列の真偽値Seriesです。other=df_with_nan['B']
は ‘B’列のSeriesです。where
は ‘A’列のnotna()
がFalse
の位置(つまり’A’列が欠損値の位置)の要素を、other=df_with_nan['B']
の対応するインデックスの値で置き換えます。
これは特定の列の欠損値を別の列の値で補完する一般的な手法です。
2. 異常値の処理
特定の範囲外の値を異常値とみなし、それらを特定のルールで置き換えることができます。
例えば、「年齢列で0未満または150より大きい値を欠損値に置き換える」という処理を考えます。
“`python
data_age = {‘Name’: [‘Alice’, ‘Bob’, ‘Charlie’, ‘David’, ‘Eve’],
‘Age’: [25, -5, 160, 42, 30],
‘Score’: [85, 90, 70, 95, 88]}
df_age = pd.DataFrame(data_age)
print(“\n年齢データを含むDataFrame:”)
print(df_age)
条件: Ageが0以上 かつ 150以下
cond_age_valid = (df_age[‘Age’] >= 0) & (df_age[‘Age’] <= 150)
print(“\n条件 (Ageの範囲内):”)
print(cond_age_valid)
where適用: Ageが範囲外の行のAgeをNaNに置換
Age列のみに条件を適用したい
df_age[‘Age’] = df_age[‘Age’].where(cond_age_valid, other=np.nan)
print(“\nwhere適用後のDataFrame (異常年齢をNaNに置換):”)
print(df_age)
``
df_age[‘Age’]
ここでも、特定の列('Age')に対してのみ条件を適用し、その列の値のみを置き換えています。Seriesに対して
where` を適用することで実現できます。
3. カテゴリカルデータの変換
特定のカテゴリに属する要素を別の値に置き換える場合にも where
が使えます。特に、isin()
メソッドと組み合わせると便利です。
例えば、「商品のステータスが ‘Pending’ または ‘Cancelled’ の場合、その価格を 0 にする」という処理を考えます。
“`python
data_product = {‘Product’: [‘A’, ‘B’, ‘C’, ‘D’, ‘E’],
‘Status’: [‘Shipped’, ‘Pending’, ‘Delivered’, ‘Cancelled’, ‘Shipped’],
‘Price’: [100, 50, 200, 75, 120]}
df_product = pd.DataFrame(data_product)
print(“\n商品データを含むDataFrame:”)
print(df_product)
条件: Statusが’Pending’または’Cancelled’であるか?
cond_status_cancel_pending = df_product[‘Status’].isin([‘Pending’, ‘Cancelled’])
print(“\n条件 (StatusがPending/Cancelled):”)
print(cond_status_cancel_pending)
otherの値: Priceを0にしたい
other_price_zero = 0
where適用: StatusがPending/Cancelledの行のPriceを0に置換
Price列にのみ条件を適用したい
df_product[‘Price’] = df_product[‘Price’].where(~cond_status_cancel_pending, other=other_price_zero) # 条件を反転!
print(“\nwhere適用後のDataFrame (特定ステータスのPriceを0に置換):”)
print(df_product)
``
~cond_status_cancel_pending
**重要な注意点**: ここではと条件を反転させています。
whereは条件が
Falseの場合に
otherで置き換えるため、「StatusがPending/Cancelledである(=
cond_status_cancel_pendingが
True)**でない**」場合に元のPriceを残し、「StatusがPending/Cancelledである(=
cond_status_cancel_pendingが
True)**場合**」に
otherで置き換えたいなら、条件を
~cond_status_cancel_pending` (論理否定)にする必要があります。
あるいは、mask
メソッドを使う方が直感的かもしれません。mask
は where
とは逆に、「条件が True
の要素」を置き換えます。
“`python
mask適用: StatusがPending/Cancelledの行のPriceを0に置換 (条件を反転しない)
df_product[‘Price’] = df_product[‘Price’].mask(cond_status_cancel_pending, other=other_price_zero) # 条件を反転しない
print(“\nmask適用後のDataFrame (特定ステータスのPriceを0に置換):”)
print(df_product)
``
mask
この例のように、特定の条件を満たす要素を置き換えたい場合はを、条件を満たさない要素を置き換えたい場合は
whereを使うのが一般的です。
mask(cond, other)は
where(~cond, other)` と等価です。どちらを使うかは、書きたい条件式に合わせて、より自然に記述できる方を選ぶと良いでしょう。
高度な使い方と引数
inplace=True
デフォルトでは、where
メソッドは新しいDataFrameを返します。元のDataFrameを直接変更したい場合は、inplace=True
を指定します。
“`python
サンプルデータフレームの再作成
data = {‘A’: [10, 20, 30, 40, 50],
‘B’: [15, 25, 35, 45, 55],
‘C’: [22, 33, 11, 44, 0]}
df_inplace = pd.DataFrame(data)
print(“元のDataFrame (inplace前):”)
print(df_inplace)
whereをinplace=Trueで適用
値が30より大きい要素を残し、それ以外を999で置換 (inplace)
df_inplace.where(df_inplace > 30, other=999, inplace=True)
print(“\nwhere適用後のDataFrame (inplace後):”)
print(df_inplace)
``
inplace=Trueを使用すると、メソッドは
Noneを返します。元のDataFrame
df_inplace` が直接変更されていることを確認してください。
注意点: inplace=True
はメモリ効率が良いとされる一方で、メソッドチェーンが難しくなる、予期しない副作用を生む可能性がある、などの理由から、最近では推奨されない傾向があります。新しいDataFrameを作成して変数に再代入する (df = df.where(...)
) 方法が一般的です。
axis
引数
axis
引数は、cond
がSeriesであり、DataFrameに適用される場合に、Pandasがどのようにアライメントとブロードキャストを行うかを制御します。
axis=None
(デフォルト):cond
がDataFrameと同じ形状であれば要素ごとに適用。cond
がSeriesの場合は、SeriesのインデックスとDataFrameのカラムでアライメントを試みます。一致しない場合はエラーや予期しない結果になる可能性があります。axis='index'
またはaxis=0
:cond
がSeriesの場合、SeriesのインデックスとDataFrameの行インデックスでアライメントを試みます。Seriesは各カラムにブロードキャストされます。axis='columns'
またはaxis=1
:cond
がSeriesの場合、SeriesのインデックスとDataFrameのカラムでアライメントを試みます。Seriesは各行にブロードキャストされます。
前の例で axis='index'
と axis='columns'
を使ったSeries条件の適用方法を既に示しました。通常、要素ごとの条件 (df > 30
のようなDataFrame条件) を使う場合は axis=None
で問題ありません。特定の列の条件でDataFrame全体を操作したい場合(かつwhereを使いたい場合)は axis='index'
と Series 条件を組み合わせることが多いです。特定の行の条件でDataFrame全体を操作したい場合は axis='columns'
と Series 条件を組み合わせることも考えられますが、これは一般的ではありません。
mask
メソッドとの比較
前述したように、mask
メソッドは where
と逆の動作をします。
df.where(cond, other)
:cond
がTrue
の要素を保持し、False
の要素をother
で置き換える。df.mask(cond, other)
:cond
がFalse
の要素を保持し、True
の要素をother
で置き換える。
したがって、df.mask(cond, other)
は df.where(~cond, other)
と論理的に等価です。どちらを使うかは、条件式 cond
をどのように記述するのが最も自然かによって選択します。例えば、「XXである場合に値を変更したい」という場合は mask(is_xx, other)
が自然ですし、「YYである場合以外に値を変更したい」という場合は where(is_yy, other)
が自然です。
where
と他のデータ操作方法の比較
Pandasには、条件に基づいてデータを操作するための様々な方法があります。where
メソッドはそれらのツールキットの一部であり、特定のユースケースで非常に効果的ですが、他の方法がより適している場合もあります。ここでは、where
と他の一般的な手法を比較します。
1. ブールインデックスとインデックス参照 []
これはPandasで条件に基づいてデータを選択する最も基本的で強力な方法です。
- 抽出:
df[condition]
やdf.loc[condition]
は、条件がTrue
である 行 を抽出します。 - 置換:
df[condition] = value
やdf.loc[condition, column_list] = value
は、条件がTrue
である 位置 の値をvalue
で置き換えます。
where
とブールインデックスによる置換との違いは、操作対象となる位置です。
df.where(cond, other)
:cond
がFalse
の位置をother
で置き換える。cond
がTrue
の位置は元の値を保持。DataFrame全体が操作対象。df[cond] = value
:cond
がTrue
の位置をvalue
で置き換える。cond
がFalse
の位置は元の値を保持。DataFrame全体が操作対象。df.loc[cond_row, cond_col] = value
:cond_row
がTrue
の行とcond_col
がTrue
の列の 交差する位置 をvalue
で置き換える。他の位置は元の値を保持。
例:特定の条件を満たさない要素を置換
「値が30より大きい要素を残し、それ以外を0で置換」というタスクを考えます。
-
where
を使う場合:
python
df_where = df.where(df > 30, other=0)
これはwhere
の典型的な使い方です。条件df > 30
がTrue
の要素は元の値、False
の要素はother=0
になります。 -
ブールインデックスによる置換を使う場合:
“`python
df_bool_indexing = df.copy()
# 条件がFalseの要素を選択し、0を代入
df_bool_indexing[df_bool_indexing <= 30] = 0 # 条件 df > 30 の逆は df <= 30あるいは、条件がTrueの要素以外を選択し、0を代入
df_bool_indexing[~(df_bool_indexing > 30)] = 0
``
df_bool_indexing <= 30
この方法では、まず置換したい位置をブールインデックスで指定し、そこにスカラー値
0を代入しています。結果は
where` を使った場合と同じになります。
どちらの方法も同じ結果を得られますが、コードの意図が異なります。
* where(cond, other)
は「cond
を満たす要素以外を other
にする」という意図を直接的に表現します。
* ブールインデックス df[~cond] = other
は「~cond
を満たす要素を選択し、other
を代入する」という手順を表現します。
どちらが読みやすいかは状況や個人の好みによりますが、where
は特に欠損値補完 (df.where(df.notna(), other)
) や異常値置換 (df.where(value_is_valid, other)
) のシナリオでその意図が明確になりやすいでしょう。
2. loc
/ iloc
とブールインデックス
loc
や iloc
をブールインデックスと組み合わせて使うと、特定の行または列の条件に基づいて、特定の行と列の交差する位置を置換できます。
例:特定の列の条件を満たす行の、特定の列の値を置換
「’A’列の値が30より大きい行の、’B’列と’C’列の値を999で置換する」というタスクを考えます。
-
where
を使う場合:
これはwhere
の直接的な得意分野ではありません。条件が単一のSeries (df['A'] > 30
) なので、DataFrame全体に適用するにはaxis='index'
と組み合わせる必要があり、他の列も巻き込んでしまう可能性があります。目的のカラムだけを操作するには、少し工夫が必要です。“`python
‘A’列の条件Series
cond_A = df[‘A’] > 30
whereを適用したいカラム (‘B’と’C’) を選択し、条件を適用
ただし、whereのcondはDataFrameと同じ形状かブロードキャスト可能である必要がある
ここでは、条件Seriesをaxis=’index’で行方向にブロードキャストさせて適用
df_where_loc_equiv = df[[‘B’, ‘C’]].where(cond_A, other=999, axis=’index’)
元のDataFrameに結果を戻す
df_loc_equiv = df.copy()
df_loc_equiv[[‘B’, ‘C’]] = df_where_loc_equivprint(“\nwhereでloc相当の置換:”)
print(df_loc_equiv)
``
where
このように、で
locと同等の操作を行うには、対象のカラムだけを選択して
whereを適用し、その結果を元のDataFrameに戻すという手順が必要になる場合があります。条件が Series で
axis=’index’` 指定の場合、「条件が True の 行 はそのまま、False の 行 は other で置換」という挙動になります。 -
loc
とブールインデックスを使う場合:
“`python
df_loc = df.copy()
# 行の選択条件: ‘A’列が30より大きい
cond_row = df_loc[‘A’] > 30
# 列の選択: ‘B’と’C’列
cols_to_replace = [‘B’, ‘C’]locを使って、条件を満たす行と指定した列の交差する位置を置換
df_loc.loc[cond_row, cols_to_replace] = 999
print(“\nlocで条件付き置換:”)
print(df_loc)
``
locを使う方法は、行と列の選択を明確に分離できるため、特定の行/列の条件に基づいて特定の行/列を操作する場合に非常に直感的で強力です。この例のようなタスクでは、
loc` を使う方が一般的で推奨されます。
where
は、条件に基づいてDataFrame/Series全体の各要素を操作するのに適しています。一方、loc
や iloc
は、行や列のラベル/位置に基づいてデータを選択・操作するのに適しており、ブールインデックスと組み合わせることで強力な条件指定が可能になります。
3. apply
/ applymap
apply
や applymap
は、より複雑な条件や要素ごとのカスタム関数を適用したい場合に利用されます。
apply
: 行または列に対して関数を適用します。条件に基づいて行/列全体を操作する場合に使えます。applymap
(または新しいpipe
): 要素ごとに関数を適用します。要素の値に基づいて複雑な条件判定や変換を行いたい場合に使えます。
例:カスタム関数を使った条件置換
「値がその列の平均値より大きい場合に元の値を保持し、そうでない場合はその列の平均値で置き換える」というタスクを考えます。
-
where
を使う場合:
この条件(列の平均値)は列ごとに異なるため、少し工夫が必要です。各列に対して条件Series (df[col] > df[col].mean()
) を作成し、その列だけにwhere
を適用するか、またはDataFrame全体に対するブロードキャスト可能な条件を作成する必要があります。DataFrame全体に対する条件は難しいです。各列にwhereを適用する方法が現実的です。“`python
df_where_apply_equiv = df.copy()
for col in df_where_apply_equiv.columns:
mean_val = df_where_apply_equiv[col].mean()
# その列の値が平均より大きいかどうかの条件Series
cond_col_greater_than_mean = df_where_apply_equiv[col] > mean_val
# whereをその列だけに適用
df_where_apply_equiv[col] = df_where_apply_equiv[col].where(cond_col_greater_than_mean, other=mean_val)print(“\nwhereでapply相当の置換:”)
print(df_where_apply_equiv)
``
where` をループと組み合わせて使っています。これはこれで可能ですが、Pandas的な洗練された書き方とは言えないかもしれません。
これは -
apply
を使う場合:
apply
を列方向 (axis=0
) に使うと、各列に対して関数を適用できます。関数内で列全体の平均値を計算し、条件に基づいて値を変換します。“`python
df_apply = df.copy()def replace_less_than_mean(col):
mean_val = col.mean()
# 列内の各要素に対して、平均より大きければそのまま、そうでなければ平均値に置換
return col.where(col > mean_val, other=mean_val)
# または numpy.where を使うこともできる
# return np.where(col > mean_val, col, mean_val)各列に対してapply関数を適用
df_apply = df_apply.apply(replace_less_than_mean, axis=0)
print(“\napplyで条件付き置換:”)
print(df_apply)
``
apply
この例では、の中で各列(Series)に対して
whereを使っています。
apply` 関数内で複雑なロジックを記述できるため、要素ごとの値だけでなく、列全体の統計量などに基づいた条件や置換が容易に行えます。
where
は要素ごとの単純な真偽値条件と置換値を直接適用するのに向いています。一方、apply
/ applymap
は、より複雑な要素ごとの計算や、行/列全体を考慮した条件・置換を定義するのに適しています。
4. replace
replace
メソッドは、DataFrameまたはSeriesの特定の値を、別の値に置き換えるために使用されます。
df.replace(to_replace, value)
:to_replace
で指定した値/パターンと完全に一致する要素をvalue
で置き換えます。
replace
は値やパターンとの完全一致に基づいて動作します。一方、where
は任意の真偽値条件に基づいて動作します。
例:特定の値の置換
「データフレーム内の全ての 30 を 3000 に置き換える」というタスクを考えます。
-
where
を使う場合:
“`python
# 値が30であるかどうかの条件
cond_is_30 = df == 30
# where適用: 30でないものを残し、30であるものを3000で置換
df_where_replace_equiv = df.where(~cond_is_30, other=3000)print(“\nwhereでreplace相当の置換:”)
print(df_where_replace_equiv)
``
where
これはを使って
replaceと同じ結果を得る方法ですが、
replace` を使う方がシンプルです。 -
replace
を使う場合:
“`python
df_replace = df.copy()
# 値30を3000に置換
df_replace = df_replace.replace(30, 3000)print(“\nreplaceで特定値の置換:”)
print(df_replace)
``
replace
特定の値または値のリストを別の値に置き換えたいだけであれば、が最も直接的で推奨される方法です。
replace` は正規表現や辞書を使った複数の置換ルールにも対応しており、値ベースの置換に特化しています。
パフォーマンスに関する考察
一般的に、PandasのメソッドはNumPy配列に対する操作を内部的に利用しているため、Pythonのループを使った処理よりもはるかに高速です。where
メソッドもベクトル化された操作であり、大規模なデータセットに対しても効率的に機能します。
where
とブールインデックスによる代入 (df[~cond] = other
) は、多くの場合、同様のパフォーマンス特性を持ちます。どちらもNumPyの where
関数(NumPyの where(condition, x, y)
は condition
がTrueなら x
、Falseなら y
を返します。Pandasの where
は引数の順番と動作が逆です)や同等のC/Cython実装に基づいています。
パフォーマンスを考える上で重要な点は以下の通りです。
- 不要なデータ型の変換を避ける: デフォルトの
other=nan
は、元のDataFrameが整数型でも浮動小数点型に変換します。これが望ましくない場合は、other
に適切な型の値を指定するか、データ型の変換コストを許容する必要があります。 inplace=True
の使用:inplace=True
は新しいオブジェクトを作成しないため、大規模なデータセットを扱う場合にメモリ使用量を抑えられる可能性があります。ただし、前述のように非推奨とされることもあります。- アライメントのコスト:
other
に DataFrame や Series を指定した場合、Pandasはインデックスとカラムのアライメントを行います。これが複雑だったり、一致しない部分が多かったりすると、パフォーマンスに影響を与える可能性があります。スカラー値による置換が最も効率的です。 - 条件作成のコスト: 複雑な条件式を作成する際のコストも考慮する必要があります。複数の条件を組み合わせる場合、中間的な真偽値Series/DataFrameが生成される可能性があります。
多くの場合、where
またはブールインデックスによる置換は、カスタム関数を要素ごとに適用する (applymap
など) よりも高速です。要素ごとの複雑な処理が必要ない場合は、これらのベクトル化されたメソッドを選択するのが良いパフォーマンスを得るための鍵となります。
where
を使う上での注意点
where
メソッドを効果的に使用するために、いくつかの注意点があります。
- 条件 (
cond
) の形状とデータ型:cond
は真偽値のDataFrame、Series、またはNumPy配列である必要があります。形状が操作対象のDataFrameと一致しない場合、Pandasはブロードキャストを試みますが、意図しない結果になる可能性があります。DataFrame全体を操作する場合は、形状が一致する真偽値DataFrameをcond
として渡すのが最も安全で明確です。特定の列/行を操作する場合は、Seriesに対してwhere
を適用したり、axis
引数を適切に使用したりする必要がありますが、その挙動を正確に理解しておく必要があります。 other
引数のアライメント:other
にDataFrameやSeriesを指定した場合、インデックスとカラムのアライメントが重要です。一致しない部分の置換結果はNaN
になる可能性が高いです。期待通りのアライメントが行われるか、またはアライメントを必要としないスカラー値を指定するかを明確に判断する必要があります。- データ型の変更:
other
に元のデータの型と異なる型の値を指定したり、デフォルトのNaN
を使用したりすると、列のデータ型が変更されることがあります(特に整数型から浮動小数点型への変更)。この変更が後続の処理に影響を与えないか確認が必要です。 inplace=True
の使用: 元のDataFrameを変更する場合は注意が必要です。特に、複数の変数で同じDataFrameを参照している場合、inplace=True
による変更は参照元の全ての変数に影響します。通常は新しいDataFrameを変数に代入する方法が推奨されます。- 欠損値 (
NaN
) の扱い: 条件式に欠損値が含まれる場合、Pandasは欠損値を含む比較の結果をFalse
として扱うことが多いですが、常にそうとは限りません(例えばNaN == NaN
はFalse
)。また、where
はデフォルトで条件がFalseの位置をNaN
で埋めます。欠損値の挙動を理解しておくことが重要です。例えば、「NaNの要素を残したい」場合は、条件がNaNの位置でTrueになるように調整する必要があります(例:df.where(df.isna(), other=...)
とすると、NaNでない要素が other に置き換えられます)。
まとめ
この記事では、Pandasの DataFrame.where()
メソッドについて、その基本的な使い方から応用、他のメソッドとの比較までを詳細に解説しました。
where
メソッドの重要なポイント:
- 指定された条件 (
cond
) が False の位置にある要素を、other
引数で指定した値で置き換えます。条件が True の要素はそのまま保持されます。 - デフォルトでは、条件がFalseの要素は
NaN
に置き換えられます。 other
引数には、スカラー値、または形状が一致するかブロードキャスト可能なDataFrame/Series/NumPy配列を指定できます。cond
は、比較演算子、論理演算子、isin()
,isna()
,notna()
など、様々な方法で作成した真偽値DataFrame/Series/NumPy配列を使用できます。axis
引数は、cond
がSeriesの場合のアライメントとブロードキャストの挙動に影響します。inplace=True
で元のDataFrameを直接変更できますが、通常は新しいDataFrameを返すデフォルトの動作が推奨されます。mask
メソッドはwhere
と逆の動作をします (mask(cond, other)
はwhere(~cond, other)
と等価)。
where
メソッドが特に有効なシナリオ:
- 特定の条件を満たさない値を一括してデフォルト値(NaNなど)や特定のスカラー値で置き換えたい場合。
- 欠損値以外の要素を保持し、欠損値を特定の値や他の列の値で補完したい場合(
df.where(df.notna(), other=...)
)。 - 異常値(特定の範囲外の値など)以外の要素を保持し、異常値を別の値(NaNなど)で置き換えたい場合(
df.where(value_is_valid, other=...)
)。 - DataFrame全体に対して、要素ごとの条件に基づく置換を行いたい場合。
他の方法との使い分け:
- 特定の条件を満たす 行 を抽出したい場合は、ブールインデックス(
df[cond]
やdf.loc[cond]
)を使用するのが最も一般的です。 - 特定の条件を満たす 位置 の値を変更したい場合は、ブールインデックスによる代入(
df[cond] = value
)やloc[cond, cols] = value
が直感的で強力です。where
は「条件を満たさない場所を置き換える」という逆の視点を提供します。 - 特定の値やパターンを置き換えたい場合は、
replace
メソッドが最も適しています。 - 行/列ごと、または要素ごとに複雑なカスタム関数や統計量に基づく条件・置換を行いたい場合は、
apply
やapplymap
が適している場合があります。
where
メソッドは、Pandasを使ったデータクリーニングや前処理において、条件に基づく置換を柔軟に行うための強力なツールです。その独特な動作(条件がFalseの場合に置換)を理解し、他の関連メソッドと比較しながら適切に使い分けることで、より効率的かつ意図が明確なデータ操作コードを記述できるようになります。
この記事が、Pandas where
メソッドの理解を深め、実際のデータ分析タスクで自信を持って活用するための一助となれば幸いです。データ操作の引き出しを増やすことで、より複雑なデータ処理にも対応できるようになるでしょう。