機械学習のためのデータ前処理:外れ値(異常値)の検出と対応方法【Pandas入門】
機械学習モデルを構築する際、データの品質はモデルの性能に大きく影響します。データ前処理は、そのデータの品質を高め、モデルがデータから適切に学習できるようにするための重要なステップです。
前回の記事では、欠損値の対処法について解説しました。データ前処理には、欠損値の他にも対処すべき様々な課題が存在します。その一つに「外れ値(異常値)」があります。
本記事では、機械学習における外れ値とは何か、なぜ対処が必要なのか、そしてPandasライブラリを使った具体的な検出・対処方法について解説します。データ分析や機械学習プロジェクトに関わるビジネスパーソンの方々が、データ前処理における外れ値の重要性を理解し、基本的な対応ができるようになることを目指します。
外れ値(異常値)とは何か?なぜ対処が必要なのか?
まず、「外れ値」とはどのようなデータなのでしょうか。外れ値は、他の多くのデータポイントから著しく離れた値を持つデータのことです。「異常値」とも呼ばれます。
例えば、ある商品の月間売上データを分析している際に、ほとんどの商品が月間数万円から数十万円の売上である中で、一つだけ数千万円といった極端に大きな売上データがあったとします。もし、その数千万円というデータが入力ミスや特殊な要因(例えば、期間限定の大型キャンペーンなど)によるもので、通常の傾向から大きく外れている場合、それは外れ値である可能性があります。
このような外れ値がデータに含まれていると、機械学習モデルの学習プロセスに悪影響を及ぼす可能性があります。特に、平均値や標準偏差などの統計量に大きく影響を与え、モデルがデータ全体の傾向を正確に捉えられなくなることがあります。結果として、モデルの予測精度が低下したり、誤った結論を導き出したりする原因となり得ます。
そのため、機械学習モデルを構築する前には、データに含まれる外れ値を適切に検出し、必要に応じて対処することが重要になります。
外れ値の検出方法
外れ値を検出するには、主に「視覚的な方法」と「統計的な方法」があります。両方の方法を組み合わせることで、より確実に外れ値を発見することができます。
1. 視覚的な方法
データをグラフとして描画することで、直感的に外れ値を発見する方法です。特に有効なのが、「散布図」や「箱ひげ図」です。
散布図: 2つの変数間の関係をプロットしたものです。他のデータポイントから大きく離れた位置にある点が外れ値の候補となります。
箱ひげ図 (Box Plot): データの分布を視覚的に捉えるのに非常に役立ちます。データの四分位数(25%, 50%, 75%)を示し、箱から伸びる「ひげ」の範囲を超えたデータポイントを外れ値として表示します。
PythonとPandas、そしてグラフ描画ライブラリであるMatplotlibやSeabornを使って、箱ひげ図を描画してみましょう。
まず、サンプルデータを含むPandas DataFrameを作成します。
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# サンプルデータの作成
data = {'売上': [10, 15, 12, 18, 14, 20, 16, 13, 17, 11, 19, 150]} # 150は外れ値の候補
df = pd.DataFrame(data)
print(df)
売上
0 10
1 15
2 12
3 18
4 14
5 20
6 16
7 13
8 17
9 11
10 19
11 150
このデータに対して箱ひげ図を描画します。
# 箱ひげ図の描画
plt.figure(figsize=(6, 4)) # 図のサイズを指定
sns.boxplot(y=df['売上']) # 縦軸に売上を指定
plt.title('売上データの箱ひげ図') # タイトル
plt.ylabel('売上 (万円)') # Y軸ラベル
plt.show() # 図を表示
![売上データの箱ひげ図のイメージ] (図のイメージ:縦軸に「売上 (万円)」、横軸に項目名(ここでは表示されないことも)、箱ひげ図が表示され、データポイント '150' が箱の上端から大きく離れた丸い点としてプロットされている様子)
この箱ひげ図を見ると、他のデータポイントの分布から大きく離れたところに一つの点が表示されていることが分かります。これが箱ひげ図が示す外れ値の候補です。このように視覚的に外れ値を発見することができます。
2. 統計的な方法
データの統計的な性質を利用して外れ値を数値的に特定する方法です。いくつかの方法がありますが、代表的なものに「Zスコア」や「四分位範囲(IQR)」を使った方法があります。
Zスコア (Z-score): 個々のデータポイントが、平均値からどれだけ標準偏差分離れているかを示す値です。一般的に、Zスコアが特定の閾値(例えば2や3)を超えるデータポイントは外れ値と見なされることがあります。
Zスコアは以下の式で計算されます。 $Z = (x - \mu) / \sigma$ ここで、$x$は個々のデータポイント、$\mu$は平均値、$\sigma$は標準偏差です。
Pandasを使ってZスコアを計算し、閾値を超えるデータを特定してみましょう。ここでは閾値を3とします。
import numpy as np
from scipy.stats import zscore
# Zスコアの計算
df['売上_Zスコア'] = np.abs(zscore(df['売上'])) # 絶対値を取る(平均より大きいか小さいかに関わらず、離れている度合いを見るため)
# Zスコアが3を超えるデータを外れ値候補として特定
outliers_zscore = df[df['売上_Zスコア'] > 3]
print("Zスコアによる外れ値候補:")
print(outliers_zscore)
Zスコアによる外れ値候補:
売上 売上_Zスコア
11 150 3.444043
計算結果を見ると、売上150万円のデータポイントのZスコアが3.44となり、閾値の3を超えていることが分かります。
四分位範囲 (IQR - Interquartile Range): データのばらつきを示す指標の一つで、第三四分位数(Q3 - 75パーセンタイル)と第一四分位数(Q1 - 25パーセンタイル)の差です(IQR = Q3 - Q1)。箱ひげ図の「箱」の範囲に相当します。
IQRを使った外れ値検出では、一般的に以下の基準が用いられます。 * Q1 - 1.5 * IQR よりも小さいデータ * Q3 + 1.5 * IQR よりも大きいデータ
Pandasを使ってIQRとこの基準による外れ値候補を特定してみましょう。
# 四分位数の計算
Q1 = df['売上'].quantile(0.25)
Q3 = df['売上'].quantile(0.75)
IQR = Q3 - Q1
print(f'Q1: {Q1}')
print(f'Q3: {Q3}')
print(f'IQR: {IQR}')
# 外れ値の基準
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f'下限値: {lower_bound}')
print(f'上限値: {upper_bound}')
# 基準から外れるデータを外れ値候補として特定
outliers_iqr = df[(df['売上'] < lower_bound) | (df['売上'] > upper_bound)]
print("\nIQRによる外れ値候補:")
print(outliers_iqr)
Q1: 12.5
Q3: 17.5
IQR: 5.0
下限値: 5.0
上限値: 25.0
IQRによる外れ値候補:
売上 売上_Zスコア
11 150 3.444043
IQRによる基準では、下限値5.0、上限値25.0となりました。売上150万円のデータは上限値25.0を大きく超えているため、外れ値候補として特定されています。
このように、統計的な方法を用いることで、数値的な基準に基づいて外れ値を自動的に検出することが可能です。
外れ値への対処方法
外れ値を検出した後、どのように対処すべきかは、その外れ値がなぜ発生したのか、データの性質、構築するモデルの種類によって異なります。主な対処方法をいくつかご紹介します。
1. 外れ値を含むデータを削除する
最も単純な方法です。検出された外れ値を含む行をデータセットから削除します。
メリット: 実装が簡単。モデルに与える極端な影響を取り除くことができる。 デメリット: 削除するデータが多すぎると、データ量が減少し、有用な情報まで失ってしまう可能性がある。
Pandasを使って外れ値(ここではIQR基準で特定されたデータ)を削除してみましょう。
# IQR基準の外れ値インデックスを取得
outlier_indices_iqr = outliers_iqr.index
# 外れ値を含む行を削除した新しいDataFrameを作成
df_cleaned_deleted = df.drop(outlier_indices_iqr)
print("外れ値を削除した後のデータ:")
print(df_cleaned_deleted)
外れ値を削除した後のデータ:
売上 売上_Zスコア
0 10 0.803261
1 15 0.152935
2 12 0.502791
3 18 0.303401
4 14 0.002469
5 20 0.603862
6 16 0.353868
7 13 0.302000
8 17 0.103469
9 11 0.653061
10 19 0.454337
元の12行から外れ値の1行が削除され、11行になっていることが分かります。
2. 外れ値を他の値で置換する(クリッピング、Imputation)
外れ値を削除するのではなく、よりデータセットの傾向に近い値に置き換える方法です。
- クリッピング(Clipping / Truncation): 外れ値を、検出基準で用いた上限値や下限値、あるいは特定のパーセンタイル値に置き換える方法です。
- 中央値や平均値での置換: 外れ値を、データセット全体の(外れ値を除いた)中央値や平均値で置き換える方法です。中央値は外れ値の影響を受けにくいため、よく用いられます。
Pandasを使って、IQR基準の上限値・下限値で外れ値をクリッピングする方法と、中央値で外れ値を置換する方法を見てみましょう。
クリッピングの例 (IQR基準の上限値・下限値で置き換え):
# 元のデータフレームをコピーして操作(元のデータは保持)
df_clipped = df.copy()
# IQR基準の外れ値を上限値・下限値で置き換え
df_clipped['売上'] = np.where(df_clipped['売上'] > upper_bound, upper_bound, df_clipped['売上'])
df_clipped['売上'] = np.where(df_clipped['売上'] < lower_bound, lower_bound, df_clipped['売上'])
print("外れ値をクリッピングした後のデータ:")
print(df_clipped)
外れ値をクリッピングした後のデータ:
売上 売上_Zスコア
0 10.0 0.803261
1 15.0 0.152935
2 12.0 0.502791
3 18.0 0.303401
4 14.0 0.002469
5 20.0 0.603862
6 16.0 0.353868
7 13.0 0.302000
8 17.0 0.103469
9 11.0 0.653061
10 19.0 0.454337
11 25.0 3.444043 # 元の150が上限値の25.0に置き換わっている
売上150万円だったデータが、上限値の25.0万円に置き換わっていることが分かります。
中央値での置換の例:
# 元のデータフレームをコピーして操作
df_imputed_median = df.copy()
# 外れ値を除いたデータの中央値を計算
median_value = df_cleaned_deleted['売上'].median()
print(f"外れ値を除いたデータの中央値: {median_value}")
# 外れ値(IQR基準)を中央値で置き換え
outlier_indices_iqr = outliers_iqr.index
df_imputed_median.loc[outlier_indices_iqr, '売上'] = median_value
print("\n外れ値を中央値で置換した後のデータ:")
print(df_imputed_median)
外れ値を除いたデータの中央値: 14.5
外れ値を中央値で置換した後のデータ:
売上 売上_Zスコア
0 10.0 0.803261
1 15.0 0.152935
2 12.0 0.502791
3 18.0 0.303401
4 14.0 0.002469
5 20.0 0.603862
6 16.0 0.353868
7 13.0 0.302000
8 17.0 0.103469
9 11.0 0.653061
10 19.0 0.454337
11 14.5 3.444043 # 元の150が中央値の14.5に置き換わっている
売上150万円だったデータが、外れ値を除いたデータの中央値14.5万円に置き換わっていることが分かります。
3. データ変換を行う
データの分布が大きく偏っている場合、対数変換や平方根変換などのデータ変換を行うことで、外れ値の影響を抑えることができる場合があります。これらの変換によってデータの分布が正規分布に近くなることで、外れ値の影響を受けやすい手法(平均値などを使用するもの)でも安定した結果が得やすくなります。
Pandasを使って対数変換を行う例です。
# 元のデータフレームをコピーして操作
df_transformed = df.copy()
# 自然対数変換(0や負の値があるとエラーになるため、必要に応じて1を加えるなどの対応が必要)
# 今回のデータは全て正なのでそのまま適用
df_transformed['売上_log'] = np.log(df_transformed['売上'])
print("売上データを対数変換した後のデータ:")
print(df_transformed)
売上データを対数変換した後のデータ:
売上 売上_Zスコア 売上_log
0 10 0.803261 2.302585
1 15 0.152935 2.708050
2 12 0.502791 2.484907
3 18 0.303401 2.890372
4 14 0.002469 2.639057
5 20 0.603862 2.995732
6 16 0.353868 2.772589
7 13 0.302000 2.564949
8 17 0.103469 2.833213
9 11 0.653061 2.397895
10 19 0.454337 2.944439
11 150 3.444043 5.010635 # 元の150が約5.01に変換されている
対数変換後のデータを見ると、元の150が約5.01に変換されており、他のデータ(2〜3程度)との差が元のデータほど大きくないことが分かります。これにより、極端な値の影響が抑えられます。
どの対処法を選ぶべきか?
どの対処法が最適かは、外れ値の性質と分析の目的に依存します。
- 削除: 外れ値が明確な入力ミスや測定エラーであり、そのデータポイント自体に意味がない場合。ただし、削除によってデータ量が大幅に減少しないか注意が必要です。
- 置換/クリッピング: 外れ値が何らかの意味を持つ可能性があるが、そのままではモデルに悪影響を与える場合。特に、データの数を減らしたくない場合に有効です。
- データ変換: データの分布自体が偏っており、外れ値が本質的な性質の一部である可能性がある場合。変換によって、外れ値だけでなくデータ全体の分布を改善できます。
実際に分析を行う際には、複数の方法を試してみて、モデルの性能が最も向上する方法を選択することが一般的です。
まとめ
本記事では、機械学習データ前処理における外れ値(異常値)の重要性、検出方法、そしてPandasを使った具体的な対処方法について解説しました。
外れ値は機械学習モデルの精度を低下させる要因となるため、適切な前処理が不可欠です。視覚的な方法(箱ひげ図など)や統計的な方法(Zスコア、IQR)を用いて外れ値を検出し、データの性質や目的に応じて削除、置換、またはデータ変換といった方法で対処します。
データ前処理は機械学習プロジェクトの成功を左右する重要なステップです。本記事が、皆様のデータ前処理スキル向上の一助となれば幸いです。次の記事では、また別の重要な前処理技術について解説していく予定です。