データ分析の基礎:Pandas DataFrame クリップによる異常値処理

2025-05-31

基本的な使い方

import pandas as pd

# サンプルDataFrameの作成
data = {'A': [1, -2, 5, 0, -5],
        'B': [-3, 4, -1, 2, 6]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

# 下限値を0に設定
df_clipped_lower = df.clip(lower=0)
print("\n下限値を0にクリップしたDataFrame:\n", df_clipped_lower)

# 上限値を3に設定
df_clipped_upper = df.clip(upper=3)
print("\n上限値を3にクリップしたDataFrame:\n", df_clipped_upper)

# 下限値を-1、上限値を4に設定
df_clipped_both = df.clip(lower=-1, upper=4)
print("\n下限値を-1、上限値を4にクリップしたDataFrame:\n", df_clipped_both)

引数の説明

  • inplace: bool, デフォルトは FalseTrue に設定すると、元のDataFrameが直接変更されます(新しいDataFrameは返されません)。
  • axis: {0 or ‘index’, 1 or ‘columns’}, デフォルトは Nonelower または upper にSeriesやDataFrameを指定した場合に、それらをDataFrameのインデックス(0または'index')または列(1または'columns')に沿って整列させるかどうかを指定します。
  • upper: スカラー値、または類似した配列(DataFrameやSeriesなど)。この値を上回る要素は、この値に置き換えられます。デフォルトは None で、上限値によるクリッピングは行われません。
  • lower: スカラー値、または類似した配列(DataFrameやSeriesなど)。この値を下回る要素は、この値に置き換えられます。デフォルトは None で、下限値によるクリッピングは行われません。

より高度な使い方

lowerupper にスカラー値だけでなく、SeriesやDataFrameを指定することもできます。この場合、DataFrameのインデックスや列ラベルに基づいて要素ごとに異なるクリッピング範囲を適用できます。

import pandas as pd

# サンプルDataFrameの作成
data = {'A': [1, -2, 5, 0, -5],
        'B': [-3, 4, -1, 2, 6]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

# 列ごとに異なる下限値を設定したSeries
lower_bound = pd.Series({'A': -1, 'B': 0})
df_clipped_series_lower = df.clip(lower=lower_bound, axis=1)
print("\n列ごとに異なる下限値でクリップしたDataFrame:\n", df_clipped_series_lower)

# DataFrame全体に対して異なる下限値と上限値を設定したDataFrame
clip_bounds = pd.DataFrame({'lower': [-2, 1, 3, -1, 0],
                            'upper': [2, 5, 6, 3, 8]})
df_clipped_df = df.clip(lower=clip_bounds['lower'], upper=clip_bounds['upper'], axis=0)
print("\n行ごとに異なる下限値と上限値でクリップしたDataFrame:\n", df_clipped_df)


TypeError: '...' object is not callable (型エラー: オブジェクトは呼び出し可能ではありません)

このエラーは、clip メソッドを関数のように呼び出してしまった場合に発生します。clip はメソッドなので、DataFrameオブジェクトに対してドット(.) でアクセスする必要があります。

誤った例

import pandas as pd

data = {'A': [1, -2, 5]}
df = pd.DataFrame(data)

# エラー: clip を関数のように呼び出している
# clipped_df = pd.clip(df, lower=0)

正しい例

import pandas as pd

data = {'A': [1, -2, 5]}
df = pd.DataFrame(data)

# 正しい: DataFrameオブジェクトのメソッドとして呼び出す
clipped_df = df.clip(lower=0)
print(clipped_df)

ValueError: Series lengths must match to compare (値エラー: Seriesの長さが比較のために一致する必要があります)

lowerupper に Series を指定し、かつ axis を指定しなかった場合、または axis の指定が DataFrame の形状と Series の形状と矛盾する場合に発生することがあります。DataFrame のインデックスまたは列ラベルと Series のインデックスが一致しない場合も同様のエラーが起こり得ます。

誤った例

import pandas as pd

data = {'A': [1, -2, 5],
        'B': [-3, 4, -1]}
df = pd.DataFrame(data)
lower_bound = pd.Series([-1, 0]) # Seriesの長さがDataFrameの列数と異なる

# エラー: Seriesの長さが一致しない
# clipped_df = df.clip(lower=lower_bound)

正しい例

import pandas as pd

data = {'A': [1, -2, 5],
        'B': [-3, 4, -1]}
df = pd.DataFrame(data)
lower_bound = pd.Series([-1, 0], index=['A', 'B']) # SeriesのインデックスをDataFrameの列名に合わせる
clipped_df = df.clip(lower=lower_bound, axis=1)
print(clipped_df)

ValueError: Shape of passed values is (x, y), indices imply (a, b) (値エラー: 渡された値の形状とインデックスが示す形状が一致しません)

lowerupper に DataFrame を指定した場合、その DataFrame の形状が元の DataFrame の形状と互換性がない場合に発生します。通常は、インデックスまたは列ラベルが一致しないことが原因です。

誤った例

import pandas as pd

data = {'A': [1, -2],
        'B': [-3, 4]}
df = pd.DataFrame(data)
clip_lower = pd.DataFrame([[-1, 0, 2], [0, 1, 3]], columns=['A', 'B', 'C']) # 列名が一部異なる

# エラー: DataFrameの形状が一致しない
# clipped_df = df.clip(lower=clip_lower)

正しい例

import pandas as pd

data = {'A': [1, -2],
        'B': [-3, 4]}
df = pd.DataFrame(data)
clip_lower = pd.DataFrame([[-1, 0], [0, 1]], columns=['A', 'B']) # 列名を一致させる
clipped_df = df.clip(lower=clip_lower)
print(clipped_df)

意図しないクリッピング結果

エラーは発生しないものの、期待した範囲でクリッピングされない場合があります。これは、lowerupper の値の指定ミス、または axis の指定ミスが原因であることが多いです。

  • 中間結果の確認
    複雑なクリッピング処理を行う場合は、途中経過の DataFrame を確認しながら進めることで、問題の原因を特定しやすくなります。
  • inplace=True の使用
    inplace=True を使用すると元の DataFrame が直接変更されるため、意図しない変更を防ぐために、操作前に DataFrame のコピーを作成しておくことを推奨します。
  • データの型
    クリップしようとしているデータの型が数値型であることを確認してください。文字列などの非数値型に対して clip を適用すると、エラーが発生するか、期待しない結果になる可能性があります。
  • axis の確認
    Series や DataFrame を lowerupper に指定した場合、axis の指定が適切かどうか確認してください。axis=0 はインデックス(行)に沿って、axis=1 は列に沿って整列させます。
  • lower と upper の値を確認
    指定した下限値と上限値が正しいか再度確認してください。


基本的な使い方 (スカラー値でのクリッピング)

import pandas as pd

# サンプルDataFrameの作成
data = {'気温': [25, 10, 35, 18, 40],
        '湿度': [60, 80, 50, 70, 45]}
df = pd.DataFrame(data, index=['A', 'B', 'C', 'D', 'E'])
print("元のDataFrame:\n", df)

# 気温を15度から30度の範囲にクリップ
df_clipped_temp = df.clip(lower=15, upper=30)
print("\n気温を15度から30度にクリップしたDataFrame:\n", df_clipped_temp)

# 湿度を55%以上にクリップ (上限値は指定しない)
df_clipped_humidity_lower = df.clip(lower=55)
print("\n湿度を55%以上にクリップしたDataFrame:\n", df_clipped_humidity_lower)

# 気温を38度以下にクリップ (下限値は指定しない)
df_clipped_temp_upper = df.clip(upper=38)
print("\n気温を38度以下にクリップしたDataFrame:\n", df_clipped_temp_upper)

この例では、DataFrame 全体の数値を指定したスカラー値の範囲内にクリップしています。lower で下限値を、upper で上限値を指定します。片方だけを指定することも可能です。

Series を使った列ごとのクリッピング

import pandas as pd

# サンプルDataFrameの作成
data = {'売上A': [100, 50, 150, 80, 200],
        '売上B': [30, 70, 20, 90, 120]}
df = pd.DataFrame(data, index=['Q1', 'Q2', 'Q3', 'Q4', 'Q5'])
print("元のDataFrame:\n", df)

# 列ごとに異なる下限値と上限値を設定したSeries
clip_range_series = pd.Series({'売上A': (70, 180), '売上B': (40, 100)})

# DataFrameの各列に対して、Seriesで指定された範囲でクリップ
df_clipped_by_series = df.clip(lower=clip_range_series.map(lambda x: x[0]),
                               upper=clip_range_series.map(lambda x: x[1]),
                               axis=1)
print("\nSeriesで指定した範囲でクリップしたDataFrame:\n", df_clipped_by_series)

ここでは、Series を使って各列ごとに異なるクリッピング範囲を指定しています。axis=1 を指定することで、列方向に Series のインデックス(列名)と DataFrame の列名を一致させてクリッピングを行います。

DataFrame を使った要素ごとのクリッピング

import pandas as pd

# サンプルDataFrameの作成
data = {'X': [1, -2, 3],
        'Y': [-1, 4, -3]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

# 下限値と上限値を要素ごとに定義したDataFrame
lower_df = pd.DataFrame([[-0.5, -1.5], [0, 2], [2.5, -2.5]], columns=['X', 'Y'])
upper_df = pd.DataFrame([[1.5, 0.5], [3, 6], [3.5, 0]], columns=['X', 'Y'])

# DataFrameで指定した下限値と上限値でクリップ
df_clipped_by_df = df.clip(lower=lower_df, upper=upper_df)
print("\nDataFrameで指定した範囲でクリップしたDataFrame:\n", df_clipped_by_df)

この例では、下限値と上限値をそれぞれ別の DataFrame で定義し、それを使って元の DataFrame をクリップしています。この場合、元の DataFrame と lower および upper の DataFrame は同じ形状である必要があります。対応する位置の要素同士でクリッピングが行われます。

条件に基づいたクリッピング (応用)

clip メソッドを直接使うわけではありませんが、条件に基づいて特定の値に置き換えることで、クリッピングと同様の効果を得ることができます。

import pandas as pd
import numpy as np

# サンプルDataFrameの作成
data = {'点数': [85, 60, 92, 78, 45]}
df = pd.DataFrame(data, index=['A', 'B', 'C', 'D', 'E'])
print("元のDataFrame:\n", df)

# 60点未満は60点、90点より大きい場合は90点に置き換える (clipと同様の効果)
df_conditional_clip = df['点数'].mask(df['点数'] < 60, 60).mask(df['点数'] > 90, 90)
print("\n条件に基づいてクリップした結果:\n", df_conditional_clip)

この例では、mask メソッドを使って、特定の条件を満たす値を別の値に置き換えることで、clip と同様の範囲制限を実現しています。



条件付きの代入 (Boolean Indexing を利用)

Boolean Indexing を使うと、特定の条件を満たす要素を選択し、それらに新しい値を代入することで、clip と同様の効果を得られます。

import pandas as pd

# サンプルDataFrameの作成
data = {'値': [-5, 2, 8, -1, 5]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

下限値 = 0
上限値 = 6

# 下限値より小さい値を下限値で置換
df.loc[df['値'] < 下限値, '値'] = 下限値
print(f"\n{下限値}を下回る値を置換:\n", df)

# 上限値より大きい値を上限値で置換
df.loc[df['値'] > 上限値, '値'] = 上限値
print(f"\n{上限値}を上回る値を置換:\n", df)

この方法では、DataFrame の要素に対して条件を作成し (df['値'] < 下限値)、その条件を満たす行の特定の列 ('値') に対して新しい値を代入しています。clip メソッドよりも明示的に条件を指定できるため、より複雑な条件に基づいた処理も可能です。

numpy.where を利用

NumPy の where 関数を使うと、条件に基づいて異なる値を要素ごとに選択することができます。これを pandas の Series や DataFrame に適用することで、クリッピングと同様の結果を得られます。

import pandas as pd
import numpy as np

# サンプルDataFrameの作成
data = {'値': [-5, 2, 8, -1, 5]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

下限値 = 0
上限値 = 6

df['クリップされた値'] = np.where(df['値'] < 下限値, 下限値,
                               np.where(df['値'] > 上限値, 上限値, df['値']))
print("\nnumpy.where でクリップした結果:\n", df)

np.where(condition, x, y) は、condition が True の要素には x を、False の要素には y を返します。この例では、ネストされた np.where を使うことで、下限値未満、上限値より大きい、範囲内の3つのケースに対応しています。

pandas.Series.mask または pandas.DataFrame.mask を利用

mask メソッドは、条件が True の要素を別の値で置換します。これを利用して、範囲外の値を範囲内の境界値で置換することで、クリッピングを実現できます。

import pandas as pd

# サンプルDataFrameの作成
data = {'値': [-5, 2, 8, -1, 5]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

下限値 = 0
上限値 = 6

df['クリップされた値'] = df['値'].mask(df['値'] < 下限値, 下限値).mask(df['値'] > 上限値, 上限値)
print("\nmask でクリップした結果:\n", df)

mask(condition, value) は、condition が True の要素を value で置換します。ここでは、下限値未満の値を下限値で、上限値より大きい値を上限値でそれぞれ置換しています。

pandas.Series.clip (Series に対して)

DataFrame の特定の列(Series)に対してのみクリッピングを行いたい場合は、pandas.Series.clip メソッドを直接使用できます。

import pandas as pd

# サンプルDataFrameの作成
data = {'値1': [-5, 2, 8, -1, 5],
        '値2': [10, -2, 15, 3, -8]}
df = pd.DataFrame(data)
print("元のDataFrame:\n", df)

下限値 = 0
上限値 = 6

df['値1_クリップ'] = df['値1'].clip(lower=下限値, upper=上限値)
print("\nSeries.clip で '値1' をクリップした結果:\n", df)

この方法は、DataFrame 全体ではなく、特定の列のみをクリップしたい場合に便利です。

  • pandas.Series.clip
    DataFrame の特定の列のみをクリップしたい場合に、より直接的なアプローチです。
  • mask メソッド
    特定の条件を満たす値を置換するという意味合いが強い場合に、コードの意図がより明確になります。
  • numpy.where
    NumPy の機能を利用したい場合や、条件に基づいて複数の選択肢から値を選びたい場合に便利です。
  • Boolean Indexing (.loc)
    より複雑な条件に基づいてクリッピングを行いたい場合や、クリッピングのロジックを明示的に記述したい場合に有効です。
  • clip メソッド
    最も直接的で簡潔な方法です。DataFrame 全体または特定の軸に沿って一律の範囲でクリッピングしたい場合に適しています。