Pandas `where`の使い方:データフレームの条件指定・置換をマスター


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 に基づいて以下のように動作します。

  1. condTrue の位置にある要素: 元のDataFrameのその位置にある値が そのまま保持されます
  2. condFalse の位置にある要素: 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 > 30True である要素(例えば 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 > 30True の要素(40, 45, 44, 50, 55, 33)は元の値のままです。条件が False の要素は全て 0 に置き換えられています。

other に DataFrame, Series, または NumPy配列を指定する場合

other 引数には、スカラー値だけでなく、DataFrame、Series、またはNumPy配列を指定することも可能です。この場合、Pandasは condother の形状を考慮して、条件を満たさない要素を other の対応する位置の値で置き換えます。

重要なのは、Pandasは cond、元のDataFrame、そして other の間でインデックスとカラム(列名)を基準にアライメント(位置合わせ)を行おうとする という点です。

  • other が元のDataFrameと全く同じ形状・インデックス・カラムを持つ場合:
    condFalse の位置にある要素は、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 0

    where適用後の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 -35

    where適用後の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
    ``
    元のDataFrameはインデックス 0, 1, 2, 3, 4、カラム A, B, C です。
    df_other_shifted_indexはインデックス 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になります)。
    * 元のDataFrameに存在しないカラム 'C' は、
    otherにも存在しないため、条件に関わらず全て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: int64

    where適用後の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: int64

    where適用後の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: int64

    where適用後の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)
``
この例では、条件が単一のSeries (
df[‘A’] < 30`) です。このSeriesはDataFrameの各行に対してブロードキャストされ、各列の要素がその行の条件(’A’列の値が30未満かどうか)で評価されます。しかし、これは通常やりたいことではありません。特定の列の条件を使ってDataFrame全体をフィルタリングしたい場合は、条件の形状に注意が必要です。

DataFrame全体に対する条件:
DataFrame全体と比較演算子を使うと、要素ごとの真偽値DataFrameが生成されます。これが wherecond として最もよく使われる形式です。

“`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 > 20Trueである要素は保持され、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]で評価されます。
* 行 0:
condition_A_less_than_30[0]True(10 < 30)。行0の全要素はそのまま。
* 行 1:
condition_A_less_than_30[1]True(20 < 30)。行1の全要素はそのまま。
* 行 2:
condition_A_less_than_30[2]False(30 < 30は False)。行2の全要素は other=999 で置換。
* 行 3:
condition_A_less_than_30[3]False(40 < 30は False)。行3の全要素は other=999 で置換。
* 行 4:
condition_A_less_than_30[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)
``
ここでも、複合条件は単一の真偽値Seriesとして生成されています。このSeriesをDataFrameの
whereに渡すと、デフォルトでは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)
``
ここでは、'C'列のみに条件を適用し、それ以外の列は条件を満たす(=元の値を保持する)ようにするために、条件DataFrame
condition_only_Cを作成しています。この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)
``
この方法では、まず対象のカラム('A'列)をSeriesとして取り出し、そのSeriesに対して
whereメソッドを適用しています。Seriesのwhereメソッドは、DataFrameのwhereと同様に動作しますが、対象がSeriesである点が異なります。condother` も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)
``
ここでも、特定の列('Age')に対してのみ条件を適用し、その列の値のみを置き換えています。
df_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_pendingTrue)**でない**」場合に元のPriceを残し、「StatusがPending/Cancelledである(=cond_status_cancel_pendingTrue)**場合**」にotherで置き換えたいなら、条件を~cond_status_cancel_pending` (論理否定)にする必要があります。

あるいは、mask メソッドを使う方が直感的かもしれません。maskwhere とは逆に、「条件が 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を返します。元のDataFramedf_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): condTrue の要素を保持し、False の要素を other で置き換える。
  • df.mask(cond, other): condFalse の要素を保持し、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] = valuedf.loc[condition, column_list] = value は、条件が True である 位置 の値を value で置き換えます。

where とブールインデックスによる置換との違いは、操作対象となる位置です。

  • df.where(cond, other): condFalse の位置を other で置き換える。condTrue の位置は元の値を保持。DataFrame全体が操作対象。
  • df[cond] = value: condTrue の位置を value で置き換える。condFalse の位置は元の値を保持。DataFrame全体が操作対象。
  • df.loc[cond_row, cond_col] = value: cond_rowTrue の行と cond_colTrue の列の 交差する位置value で置き換える。他の位置は元の値を保持。

例:特定の条件を満たさない要素を置換

「値が30より大きい要素を残し、それ以外を0で置換」というタスクを考えます。

  • where を使う場合:
    python
    df_where = df.where(df > 30, other=0)

    これは where の典型的な使い方です。条件 df > 30True の要素は元の値、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 とブールインデックス

lociloc をブールインデックスと組み合わせて使うと、特定の行または列の条件に基づいて、特定の行と列の交差する位置を置換できます。

例:特定の列の条件を満たす行の、特定の列の値を置換

「’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_equiv

    print(“\nwhereでloc相当の置換:”)
    print(df_loc_equiv)
    ``
    このように、
    wherelocと同等の操作を行うには、対象のカラムだけを選択して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全体の各要素を操作するのに適しています。一方、lociloc は、行や列のラベル/位置に基づいてデータを選択・操作するのに適しており、ブールインデックスと組み合わせることで強力な条件指定が可能になります。

3. apply / applymap

applyapplymap は、より複雑な条件や要素ごとのカスタム関数を適用したい場合に利用されます。

  • 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 == NaNFalse)。また、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 メソッドが最も適しています。
  • 行/列ごと、または要素ごとに複雑なカスタム関数や統計量に基づく条件・置換を行いたい場合は、applyapplymap が適している場合があります。

where メソッドは、Pandasを使ったデータクリーニングや前処理において、条件に基づく置換を柔軟に行うための強力なツールです。その独特な動作(条件がFalseの場合に置換)を理解し、他の関連メソッドと比較しながら適切に使い分けることで、より効率的かつ意図が明確なデータ操作コードを記述できるようになります。

この記事が、Pandas where メソッドの理解を深め、実際のデータ分析タスクで自信を持って活用するための一助となれば幸いです。データ操作の引き出しを増やすことで、より複雑なデータ処理にも対応できるようになるでしょう。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール