Pandas dropna入門:欠損値処理の基本
データ分析の世界では、欠損値(Missing Values)は避けられない問題の一つです。データ収集の段階でのエラー、調査への無回答、システムの故障、データの結合ミス、計算結果が定義されない場合など、様々な原因でデータに欠損が生じます。これらの欠損値を適切に処理することは、正確な分析結果や信頼性の高い機械学習モデルを構築するために不可欠です。
PandasはPythonでデータ分析を行う際に非常に強力なライブラリであり、欠損値を扱うための豊富な機能を提供しています。その中でも、最も基本的で広く使われる機能の一つが、欠損値を含む行や列を削除するdropnaメソッドです。
この記事では、Pandasのdropnaメソッドに焦点を当て、その基本的な使い方から様々なオプション、応用例、そして使う上での注意点までを詳細に解説します。約5000語にわたるこの解説を通じて、あなたがPandasを使ったデータクレンジングにおいてdropnaを効果的に活用できるようになることを目指します。
はじめに:なぜ欠損値処理が必要なのか?
データ分析のワークフローは、データの収集、クリーニング、変換、分析、可視化といった一連のプロセスを経て行われます。この中で、データのクリーニングは最も時間のかかる作業の一つと言われています。そのクリーニング作業の核心をなすのが、欠損値、外れ値、重複データといったデータ品質の問題に対処することです。
特に欠損値は、多くの統計手法や機械学習アルゴリズムが、完全なデータセットを前提としているため、そのままでは適用できない原因となります。例えば、平均値、標準偏差、相関係数といった基本的な統計量も、欠損値が存在すると正確に計算できないことがあります。また、機械学習モデルによっては、欠損値があるとエラーになったり、学習が正しく行われなかったりします。
欠損値を処理する方法はいくつかあります。
- 削除 (Deletion): 欠損値を含む行や列をデータセットから取り除く方法です。シンプルですが、データの情報が失われるというデメリットがあります。
- 補完 (Imputation): 欠損値を推定値(平均値、中央値、最頻値、回帰分析による予測値など)で埋める方法です。データの情報損失を抑えられますが、推定値が真の値と異なる可能性や、補完方法によるバイアスの導入といったリスクがあります。
- 欠損値をそのまま扱う手法: 欠損値を明示的に扱うことができる統計手法や機械学習アルゴリズム(例:決定木、一部の線形モデル、欠損値を考慮したEMアルゴリズムなど)を使用する方法です。
dropnaメソッドは、この「削除」というアプローチを実現するためのPandasの主要な機能です。シンプルであるがゆえに、その影響を十分に理解した上で使用することが重要になります。
この記事では、dropnaメソッドの機能を中心に解説しますが、その過程で他の欠損値処理手法についても触れ、dropnaがデータ分析のワークフローにおいてどのような位置づけにあるのかを明確にしていきます。
Pandasにおける欠損値の表現
Pandasは、Python標準ライブラリやNumPyと連携して動作するため、いくつかの異なる方法で欠損値を表現します。主な欠損値表現は以下の通りです。
NaN(Not a Number): これはNumPyに由来する浮動小数点型の特殊な値です。数値データにおける欠損値を表現するためによく使われます。Pandasの多くの数値演算はNaNを適切に扱います(例:NaNを含む合計は通常NaNになる)。また、整数型の列にNaNが混ざると、その列全体のデータ型が浮動小数点型(float64)に自動的に変換されるという特性があります。None: これはPythonの組み込みオブジェクトで、値が存在しないことを表します。Pandasのオブジェクト型(dtype('O'))や一部の拡張データ型(例:Int64Dtype())で欠損値を表現するために使われます。オブジェクト型の列では、NaNとNoneの両方が欠損値として扱われることがあります。NaT(Not a Time): これはPandasに固有の時系列データ(Datetime、Timestampなど)における欠損値を表現するための特殊な値です。日付や時間の欠損を表すために使用されます。
Pandasの多くの欠損値関連機能(isnull, isna, dropna, fillnaなど)は、これらNaN, None, NaTといった異なる表現を内部的に適切に「欠損値」として認識し、統一的に扱うことができます。これは、ユーザーが欠損値の内部的な表現の違いを意識することなく、一貫した方法で処理を行えるようにするためのPandasの重要な設計思想です。
欠損値が存在するかどうかを確認するには、isnull() (またはそのエイリアスであるisna()) メソッドを使用します。これはDataFrameやSeriesと同じ形状の真偽値(True/False)のオブジェクトを返します。Trueの場所が欠損値であることを示します。逆に、欠損値ではないことを確認するにはnotnull()を使用します。
“`python
import pandas as pd
import numpy as np
サンプルDataFrameの作成
data = {‘col1’: [1, 2, np.nan, 4, 5],
‘col2’: [‘A’, ‘B’, ‘C’, None, ‘E’],
‘col3’: [True, False, True, False, np.nan],
‘col4’: pd.to_datetime([‘2023-01-01’, ‘2023-01-02’, ‘NaT’, ‘2023-01-04’, ‘2023-01-05’])}
df = pd.DataFrame(data)
print(“元のDataFrame:”)
print(df)
print(“\nDataFrameの情報:”)
df.info()
print(“\n欠損値の確認 (isnull()):”)
print(df.isnull())
print(“\n列ごとの欠損値の数:”)
print(df.isnull().sum())
print(“\n全体の欠損値の数:”)
print(df.isnull().sum().sum())
“`
実行結果のポイント:
df.info()を見ると、各列の非欠損値の数が表示されます。合計の行数(この場合は5行)と比較することで、欠損値の存在を確認できます。col1,col2,col3,col4の全てで非Nullカウントが5未満になっています。df.isnull()は、元のDataFrameと同じ形状で、各要素が欠損値かどうかを示す真偽値のDataFrameを返します。np.nan,None,NaTがいずれもTrueとして扱われていることがわかります。df.isnull().sum()は、各列におけるTrueの数、すなわち欠損値の数を集計します。df.isnull().sum().sum()は、DataFrame全体の欠損値の総数を計算します。
このように、dropnaを使う前に、まずはinfo(), isnull(), isna(), notnull(), isnull().sum()などを活用して、データセットに欠損値がどの程度、どのように分布しているのかを把握することが非常に重要です。
dropnaメソッドの基本
dropnaメソッドは、DataFrameまたはSeriesオブジェクトのメソッドとして呼び出されます。最も基本的な使い方は、引数を何も指定せずに呼び出すことです。
“`python
dfは前述のサンプルDataFrameを使用
df_dropped_basic = df.dropna()
print(“\ndropna() の実行結果 (デフォルト動作):”)
print(df_dropped_basic)
“`
デフォルト動作の詳細:
引数を何も指定しない場合、dropna()は以下のデフォルト設定で動作します。
axis='index'(またはaxis=0): 欠損値を含む「行」を削除します。how='any': 指定された軸(デフォルトでは行)において、1つでも欠損値が含まれる場合に、その行全体を削除します。inplace=False: 削除操作の結果を新しいDataFrameとして返します。元のDataFrameは変更されません。thresh=None: このパラメータは指定されず、howパラメータのみが削除の基準となります。subset=None: 全ての行/列(axisで指定された方)を対象に欠損値の判定を行います。
上記のサンプルDataFrame df を見ると、
* 0行目: 欠損値なし
* 1行目: 欠損値なし
* 2行目: col1とcol3とcol4に欠損値 (np.nan, np.nan, NaT)
* 3行目: col2に欠損値 (None)
* 4行目: col3に欠損値 (np.nan)
デフォルト設定 (axis='index', how='any') では、2行目、3行目、4行目はそれぞれ1つ以上の欠損値を含むため削除されます。結果として、欠損値を全く含まない0行目と1行目だけが残ります。
デフォルト動作のdropna()は非常に強力ですが、データセットによってはほとんど全ての行が削除されてしまい、分析に必要なデータ量が極端に減ってしまう可能性があります。そのため、dropnaを使用する際は、その影響を十分に理解し、必要に応じてオプションを適切に指定することが重要です。
inplaceパラメータ:
デフォルトではinplace=Falseとなっており、dropnaは変更を元のDataFrameに適用せず、結果を格納した新しいDataFrameを返します。
“`python
元のDataFrameは変更されない
print(“\n元のDataFrame (dropna()実行後も変更なし):”)
print(df)
inplace=True を使う場合 (非推奨の場合が多いが、大規模データでメモリ節約になることも)
df_copy = df.copy() # 変更を加える前にコピーを作成することが推奨される
df_copy.dropna(inplace=True)
print(“\ninplace=True の実行結果:”)
print(df_copy)
“`
inplace=Trueを使用すると、メソッドはNoneを返し、元のDataFrameが直接変更されます。これはメモリの使用量を抑えることができますが、メソッドチェーンが使えなくなる、予期せぬ副作用(元のオブジェクトが変更されてしまう)のリスクがあるといった理由から、最近のPandasのスタイルではinplace=Trueは非推奨とされる傾向にあります。通常は新しいオブジェクトを返すデフォルトの挙動を利用し、必要に応じて新しい変数に代入するか、メソッドチェーンで処理を繋げるのがより一般的です。
axisパラメータ:削除する軸を指定する
axisパラメータは、欠損値を含む行を削除するか、欠損値を含む列を削除するかを指定します。
axis='index'(またはaxis=0): 欠損値を含む「行」を削除します。(デフォルト)axis='columns'(またはaxis=1): 欠損値を含む「列」を削除します。
“`python
dfは前述のサンプルDataFrameを使用
欠損値を含む列を削除 (デフォルト how=’any’)
df_dropped_cols = df.dropna(axis=’columns’)
print(“\ndropna(axis=’columns’) の実行結果:”)
print(df_dropped_cols)
“`
上記のサンプルDataFrame df では、col1, col2, col3, col4の全ての列に少なくとも1つ以上の欠損値が含まれています。したがって、axis='columns'かつデフォルトのhow='any'を指定した場合、これらの欠損値を含む列は全て削除され、結果として空のDataFrameが返されます。
どちらの軸を選ぶか?
axisを'index'にするか'columns'にするかは、分析の目的やデータの特性によって異なります。
-
行の削除 (
axis='index'):- 欠損値を含む個々の観測データ(行)が分析にとって利用できない場合。
- 欠損値の数が少なく、行を削除しても全体のデータ量への影響が小さい場合。
- 特定の観測データが完全に不完全であり、補完が適切でない場合。
- 例: ある顧客の全ての購入データが欠損している場合、その顧客の行を削除する。
-
列の削除 (
axis='columns'):- 特定の変数(列)の欠損値が非常に多く、その列全体が分析にとって意味を持たない場合。
- 特定の変数が分析目的から不要な場合。
- 例: ほとんどのデータポイントで「アンケートの自由記述欄」が空欄である場合、その列を削除する。
一般的には、行を削除するケースの方が多いかもしれません。しかし、特定の列の欠損率が極端に高い場合は、その列全体を削除することを検討する価値があります。
howパラメータ:削除の条件を指定する
howパラメータは、axisで指定された軸(行または列)において、どの程度の欠損値が存在すれば削除するかを決定します。
how='any'(デフォルト): 軸全体を見て、1つでも欠損値が含まれていれば削除します。how='all': 軸全体を見て、全ての要素が欠損値である場合にのみ削除します。
“`python
dfは前述のサンプルDataFrameを使用
how=’any’ の例 (axis=’index’はデフォルトなので省略可)
print(“\ndropna(how=’any’) の実行結果 (axis=’index’ と組み合わせ):”)
print(df.dropna(how=’any’)) # 0行目, 1行目のみ残る
how=’all’ の例 (axis=’index’ と組み合わせ)
サンプルデータに全ての列がNaNの行がないため、how=’all’では何も削除されない
df_all_nan_row = pd.DataFrame({‘col1’: [1, np.nan, np.nan],
‘col2’: [‘A’, np.nan, np.nan],
‘col3′: [True, np.nan, np.nan]})
print(“\nhow=’all’, axis=’index’ の実行結果 (全ての要素がNaNの行のみ削除):”)
print(df_all_nan_row.dropna(how=’all’))
how=’any’, axis=’columns’ の例
print(“\ndropna(how=’any’, axis=’columns’) の実行結果:”)
print(df.dropna(how=’any’, axis=’columns’)) # 全ての列にNaNがあるので、空のDataFrameになる
how=’all’, axis=’columns’ の例
サンプルデータに全ての行がNaNの列がないため、how=’all’では何も削除されない
df_all_nan_col = pd.DataFrame({‘col1’: [1, 2, 3],
‘col2’: [np.nan, np.nan, np.nan],
‘col3’: [‘A’, ‘B’, ‘C’]})
print(“\nhow=’all’, axis=’columns’ の実行結果 (全ての要素がNaNの列のみ削除):”)
print(df_all_nan_col.dropna(how=’all’, axis=’columns’))
“`
how='any' と how='all' の使い分け:
how='any': 非常に厳格な欠損値処理を行います。少しでも欠損値があるデータポイント(行または列)は許容しない場合に適しています。データの質を高く保ちたい場合や、欠損値があるとアルゴリズムが動作しない場合に有効です。ただし、データの大部分が欠損値を含む場合、多くのデータが失われるリスクがあります。how='all': 軸全体が完全に欠損値である場合にのみ削除します。これは通常、完全に空であるか、あるいは何らかの理由でデータが全く記録されなかった行や列を削除するために使用されます。例えば、データ収集システムが一時的に停止し、特定の期間の全てのデータが欠損しているような行を削除したい場合などに有用です。how='all'はhow='any'に比べてデータの削除量が少なくなる傾向があります。
threshパラメータ:非欠損値の数を基準にする
threshパラメータは、軸(行または列)を削除するかどうかの基準として、非欠損値(つまり、有効な値)の最小数を指定します。thresh=Nと指定した場合、その軸(行または列)にN個以上の非欠損値が含まれていれば、その軸は保持され、そうでなければ削除されます。
threshパラメータを使用する場合、howパラメータは通常指定しません。threshが指定されると、その値が優先され、howは無視されるか、あるいはhow='any'と組み合わせて使われることになりますが、thresh単独で削除の基準を指定する方が意図が明確になります。
“`python
dfは前述のサンプルDataFrameを使用
print(“\n元のDataFrame:”)
print(df)
thresh=3 の例 (axis=’index’ はデフォルト)
各行の非欠損値が3つ以上の行を残す
df_dropped_thresh_3 = df.dropna(thresh=3)
print(“\ndropna(thresh=3) の実行結果 (axis=’index’):”)
print(df_dropped_thresh_3)
thresh=5 の例 (axis=’index’)
各行の非欠損値が5つ以上の行を残す (= 全ての要素が非欠損値の行)
df_dropped_thresh_5 = df.dropna(thresh=5)
print(“\ndropna(thresh=5) の実行結果 (axis=’index’):”)
print(df_dropped_thresh_5)
thresh=2 の例 (axis=’columns’)
各列の非欠損値が2つ以上の列を残す
df_dropped_thresh_2_cols = df.dropna(thresh=2, axis=’columns’)
print(“\ndropna(thresh=2, axis=’columns’) の実行結果:”)
print(df_dropped_thresh_2_cols)
“`
実行結果の解説:
-
dropna(thresh=3):- 0行目: 非欠損値 4つ (
col1,col2,col3,col4) -> 残る (4 >= 3) - 1行目: 非欠損値 4つ (
col1,col2,col3,col4) -> 残る (4 >= 3) - 2行目: 非欠損値 1つ (
col2) -> 削除 (1 < 3) - 3行目: 非欠損値 3つ (
col1,col3,col4) -> 残る (3 >= 3) - 4行目: 非欠損値 3つ (
col1,col2,col4) -> 残る (3 >= 3)
結果として、2行目のみが削除され、0, 1, 3, 4行目が残ります。
- 0行目: 非欠損値 4つ (
-
dropna(thresh=5):- DataFrameの列数は4です。したがって、非欠損値が5つ以上の行は存在しません。全ての行が削除され、空のDataFrameが返されます。
threshは非欠損値の「数」であり、「割合」ではないことに注意してください。この場合、thresh=4を指定すれば、全ての列が非欠損値の行(0行目と1行目)が残ります。
- DataFrameの列数は4です。したがって、非欠損値が5つ以上の行は存在しません。全ての行が削除され、空のDataFrameが返されます。
-
dropna(thresh=2, axis='columns'):col1: 非欠損値 4つ -> 残る (4 >= 2)col2: 非欠損値 4つ -> 残る (4 >= 2)col3: 非欠損値 3つ -> 残る (3 >= 2)col4: 非欠損値 4つ -> 残る (4 >= 2)
全ての列が非欠損値2つ以上の条件を満たすため、どの列も削除されません。元のDataFrameと同じ列構成のDataFrameが返されます。
threshパラメータは、特定の割合以上のデータが揃っている行や列だけを残したい場合に非常に有用です。例えば、アンケートデータで「少なくとも必須項目N個が回答されている」回答者だけを残したい、といったシナリオに活用できます。
また、threshとhow='all'は密接に関連しています。thresh=Nは、非欠損値の数がN以上であることを要求します。how='all'は、非欠損値の数が少なくとも1つであることを要求します(全ての要素が欠損値である場合に削除する、ということは、1つでも非欠損値があれば残す、ということと同じ)。したがって、dropna(how='all', axis=0)は、dropna(thresh=1, axis=0)と実質的に同じ結果になります(要素が1つしかないSeriesでない限り)。
subsetパラメータ:判定対象のサブセットを指定する
subsetパラメータは、axisで指定された軸(行または列)を削除するかどうかの判定を、DataFrameの特定の列または特定の行(インデックス)に限定するために使用します。リスト形式で列名またはインデックス名を指定します。
axis='index'(行削除):subsetには列名のリストを指定します。これらの指定された列のいずれか(how='any'の場合)または全て(how='all'の場合)に欠損値が存在する行が削除されます。axis='columns'(列削除):subsetにはインデックス名のリストを指定します。これらの指定されたインデックス(行)のいずれか(how='any'の場合)または全て(how='all'の場合)に欠損値が存在する列が削除されます。
一般的に、axis='index'でsubsetに列名のリストを指定するケースが多いです。これは、「特定の重要な列(例えば、ID、キーとなる属性、目的変数など)に欠損値がある行を削除したい」というシナリオによく対応するためです。
“`python
dfは前述のサンプルDataFrameを使用
print(“\n元のDataFrame:”)
print(df)
axis=’index’, subset=[‘col1’, ‘col2′] の例 (デフォルト how=’any’)
‘col1’ または ‘col2’ のどちらかに欠損値がある行を削除
df_dropped_subset = df.dropna(subset=[‘col1’, ‘col2’])
print(“\ndropna(subset=[‘col1’, ‘col2′]) の実行結果 (axis=’index’, how=’any’):”)
print(df_dropped_subset)
axis=’index’, subset=[‘col1’, ‘col3′], how=’all’ の例
‘col1’ かつ ‘col3’ の両方に欠損値がある行を削除
df_dropped_subset_how_all = df.dropna(subset=[‘col1’, ‘col3′], how=’all’)
print(“\ndropna(subset=[‘col1’, ‘col3′], how=’all’) の実行結果 (axis=’index’):”)
print(df_dropped_subset_how_all)
axis=’columns’, subset=[0, 1] の例 (デフォルト how=’any’)
インデックス 0 または 1 のどちらかに欠損値がある列を削除
df_dropped_subset_cols = df.dropna(axis=’columns’, subset=[0, 1])
print(“\ndropna(axis=’columns’, subset=[0, 1]) の実行結果:”)
print(df_dropped_subset_cols)
“`
実行結果の解説:
-
dropna(subset=['col1', 'col2']):- 0行目:
col1もcol2も欠損値なし -> 残る - 1行目:
col1もcol2も欠損値なし -> 残る - 2行目:
col1に欠損値あり -> 削除 (col1orcol2に欠損があるため) - 3行目:
col2に欠損値あり -> 削除 (col1orcol2に欠損があるため) - 4行目:
col1もcol2も欠損値なし -> 残る
結果として、0行目、1行目、4行目が残ります。
- 0行目:
-
dropna(subset=['col1', 'col3'], how='all'):- 0行目:
col1もcol3も欠損値なし - 1行目:
col1もcol3も欠損値なし - 2行目:
col1に欠損値あり、col3に欠損値あり -> 削除 (col1andcol3の両方に欠損があるため) - 3行目:
col1は非欠損値、col3は非欠損値 - 4行目:
col1は非欠損値、col3に欠損値あり
結果として、2行目のみが削除され、0, 1, 3, 4行目が残ります。
- 0行目:
-
dropna(axis='columns', subset=[0, 1]):col1: インデックス0, 1の値はどちらも非欠損値 -> 残るcol2: インデックス0, 1の値はどちらも非欠損値 -> 残るcol3: インデックス0, 1の値はどちらも非欠損値 -> 残るcol4: インデックス0, 1の値はどちらも非欠損値 -> 残る
サンプルデータの場合、インデックス0と1の行には欠損値が全く含まれていません。したがって、subset=[0, 1]で欠損判定を行っても、どの列もこの条件に該当しないため、どの列も削除されません。
subsetパラメータは、DataFrame全体ではなく、特定の関心のある部分に基づいて欠損値を削除したい場合に非常に強力です。特に、データのキーとなる情報や、モデルの目的変数など、欠損が許されない特定の列が明確な場合に便利です。
dropnaメソッドの応用例
これまでに解説したパラメータを組み合わせることで、様々な欠損値処理のシナリオに対応できます。ここではいくつかの応用例を示します。
応用例1:特定の列の欠損値を持つ行だけを削除
最も一般的なケースの一つです。例えば、顧客データにおいて、顧客ID(CustomerID列)が欠損している行は識別不能なので削除したい、といった場合です。
“`python
import pandas as pd
import numpy as np
サンプルデータ (顧客IDと年齢に欠損がある場合を想定)
data = {‘CustomerID’: [101, 102, np.nan, 104, 105, np.nan],
‘Age’: [25, 30, 22, np.nan, 40, 35],
‘Income’: [50000, 60000, 55000, 70000, np.nan, 65000]}
df_customers = pd.DataFrame(data)
print(“元の顧客DataFrame:”)
print(df_customers)
CustomerIDが欠損している行のみを削除
axis=’index’ はデフォルト、how=’any’ もデフォルトだが、
subset=’CustomerID’ が指定されているため、’CustomerID’ 列のみを見て判定
df_customers_cleaned = df_customers.dropna(subset=[‘CustomerID’])
print(“\nCustomerIDが欠損している行を削除した結果:”)
print(df_customers_cleaned)
“`
この例では、dropna(subset=['CustomerID'])とすることで、他の列(AgeやIncome)に欠損があっても、CustomerID列に欠損がない行は保持されます。CustomerIDがnp.nanであるインデックス2と5の行が削除されました。
応用例2:複数の重要な列にデータが揃っている行だけを残す
アンケートデータで、回答者の基本情報(年齢、性別など)と主要な質問への回答が全て揃っている回答者だけを分析対象としたい、といった場合です。
“`python
df_customersを再利用
AgeとIncomeの両方にデータがある行のみを残す (つまり、どちらかでも欠損している行は削除)
how=’any’ (デフォルト) と subset を組み合わせ
df_customers_complete_info = df_customers.dropna(subset=[‘Age’, ‘Income’], how=’any’)
print(“\nAge または Income に欠損がある行を削除した結果:”)
print(df_customers_complete_info)
“`
dropna(subset=['Age', 'Income'], how='any')とすることで、Age列またはIncome列のどちらかに欠損値がある行(インデックス3と4)が削除されます。インデックス2の行は、AgeとIncomeは非欠損値ですが、CustomerIDが欠損値です。しかし、判定対象はAgeとIncome列に限定されているため、この行は削除されずに残ります。
応用例3:特定の割合以上のデータが揃っている列だけを残す
例えば、欠損率が70%を超えるような、ほとんどのデータが欠損している列は分析に役に立たないため削除したい、といった場合です。
“`python
import pandas as pd
import numpy as np
サンプルデータ (欠損率の高い列を含む)
data = {‘colA’: [1, 2, 3, 4, 5],
‘colB’: [‘A’, np.nan, ‘C’, np.nan, ‘E’],
‘colC’: [np.nan] * 5, # 全て欠損
‘colD’: [True, False, np.nan, False, np.nan]}
df_sparse = pd.DataFrame(data)
print(“元のスパースなDataFrame:”)
print(df_sparse)
print(“\n元のスパースなDataFrameの欠損値カウント:”)
print(df_sparse.isnull().sum())
非欠損値が3つ以上の列を残す (つまり、欠損率が (5-3)/5 = 40% 以下の列を残す)
thresh_value = 3 # 5行中3行以上データがある
df_dense_cols = df_sparse.dropna(axis=’columns’, thresh=thresh_value)
print(f”\n非欠損値が {thresh_value} つ以上の列を削除した結果:”)
print(df_dense_cols)
“`
df_sparseの欠損値カウントは以下のようになります。
* colA: 0個
* colB: 2個 (非欠損値 3個)
* colC: 5個 (非欠損値 0個)
* colD: 2個 (非欠損値 3個)
dropna(axis='columns', thresh=3)は、非欠損値が3つ以上の列を残します。
* colA: 非欠損値 5個 -> 残る (5 >= 3)
* colB: 非欠損値 3個 -> 残る (3 >= 3)
* colC: 非欠損値 0個 -> 削除 (0 < 3)
* colD: 非欠損値 3個 -> 残る (3 >= 3)
結果として、colCのみが削除されます。
dropnaを使う上での注意点と考慮事項
dropnaは欠損値処理の強力なツールですが、無思考に使うとデータ分析に悪影響を与える可能性があります。使用する前に、以下の点を十分に考慮する必要があります。
- 情報損失とサンプルサイズの減少:
dropnaは文字通りデータの一部を削除するため、利用可能なデータ量が減少します。これは、統計的検定の検出力の低下や、機械学習モデルの学習データ不足につながる可能性があります。特に、データセットが元々小さい場合は、dropnaを使用することで分析が不可能になることもあります。 - バイアスの導入: 欠損値の発生が完全にランダムでない場合(MAR: Missing At Random や MNAR: Missing Not At Random の場合)、特定の属性を持つデータや特定の条件下で収集されたデータに欠損値が偏って存在することがあります。このような状況で欠損値を含む行を削除すると、残ったデータが元の全体のデータを代表しなくなり、分析結果にバイアスが生じる可能性があります。例えば、高齢者や低所得者層でアンケートの欠損が多い場合、単純に
dropnaすると、分析対象が若い層や高所得者層に偏り、全体傾向を誤って把握する可能性があります。 - 欠損メカニズムの理解の重要性: 欠損値がなぜ発生したのか、どのようなパターンで欠損しているのかを理解することは、適切な処理方法を選択する上で非常に重要です。
dropnaは、欠損が完全にランダムであると仮定できる場合や、欠損値を含むデータが分析対象として完全に無効である場合に最も適しています。そうでない場合は、補完や欠損値を扱えるモデルの検討が必要になります。df.isnull().sum()や、欠損値のパターンを可視化するライブラリ(例:missingno)などが、欠損メカニズムを理解するのに役立ちます。 inplace=Trueの利用: 前述したように、inplace=Trueは元のDataFrameを変更するため、メソッドチェーンが使えなくなるなどのデメリットがあります。特別な理由がない限り、inplace=False(デフォルト)のまま使用し、結果を新しい変数に代入するか、チェーン処理を行うのが推奨されます。- インデックスのリセット:
dropnaで行を削除すると、元のDataFrameのインデックスが保持されます。連番のインデックスが必要な場合(例: 後の処理で.ilocを使う場合や、データをNumPy配列に変換する場合)、dropnaの後に.reset_index(drop=True)を実行することを検討してください。
“`python
df_dropped_basic は上で dropna() した結果 (0行目と1行目のみ残存)
print(“\ndropna後のインデックス:”)
print(df_dropped_basic)
インデックスをリセット
df_reset_index = df_dropped_basic.reset_index(drop=True)
print(“\ndropna後にreset_index(drop=True)を実行した結果:”)
print(df_reset_index)
“`
reset_index(drop=True)とすることで、元のインデックス列が新しい列として追加されるのを防ぎつつ、インデックスを0からの連番に振り直すことができます。
dropna以外の欠損値処理方法の簡単な紹介
dropnaがデータの削除であるのに対し、欠損値を何らかの値で「埋める」補完(Imputation)は、データの情報損失を抑えるための重要な代替手段です。Pandasではfillnaメソッドがその機能を提供します。
fillna(): 欠損値を指定した値(例: 0, 平均値, 中央値, 固定値など)や、前後の有効な値で埋めることができます。
python
# 例: col1の欠損値を平均値で補完
df_filled = df.fillna({'col1': df['col1'].mean()})
print("\nfillna() による欠損値補完の例:")
print(df_filled)
fillnaは、欠損値処理のもう一つの柱であり、dropnaと組み合わせて使用されることもあります(例: 欠損率が低い列はfillnaで補完し、欠損率が高い列はdropnaで削除する)。
また、より高度な補間方法として、線形補間(interpolate())、回帰モデルによる予測、KNN(k-近傍法)ベースの補間などがあります。これらは、欠損値の周囲の値や他の列との関係性に基づいて欠損値を推定します。
これらの補完方法や、欠損値を直接扱える統計手法/機械学習アルゴリズムの存在を知っておくことは、dropnaを選択する際の判断材料となります。dropnaはシンプルで高速ですが、常に最善の選択肢とは限りません。
dropnaと他のPandas機能の組み合わせ
dropnaは単独で使用されるだけでなく、他のPandasの機能と組み合わせてデータ分析の前処理パイプラインの一部として使用されることがよくあります。
- フィルタリング/条件抽出:
dropnaは欠損値の有無に基づいて行/列を削除しますが、特定の条件を満たす行/列を抽出するには、ブールインデックス(Boolean Indexing)を使用します。例えば、「年齢が30歳以上」かつ「欠損値がない」顧客だけを抽出したい場合などです。
python
# df_customers_cleaned (CustomerIDに欠損がないDataFrame) を使用
# 年齢が欠損していない かつ 年齢が30以上の行を抽出
df_filtered = df_customers_cleaned[df_customers_cleaned['Age'].notnull() & (df_customers_cleaned['Age'] >= 30)]
print("\ndropnaとフィルタリングの組み合わせ:")
print(df_filtered) - 集計 (
groupby) の前処理:groupbyなどの集計を行う前にdropnaで欠損値を含む行を削除することは一般的です。これは、集計結果に欠損値が影響を与えないようにするためです。ただし、欠損値自体が意味を持つ場合(例: 未回答を「不明」として集計したい場合)は、dropnaではなくfillnaなどで欠損値を特定のカテゴリに置き換える方が適切なこともあります。 - ソート (
sort_values):dropnaで欠損値を削除した後、特定の列の値でソートすることで、分析しやすいようにデータを整理することができます。
パフォーマンスに関する考慮事項
大規模なデータセットを扱う場合、dropnaのパフォーマンスも考慮に入れるべき点です。
- 新しいオブジェクトの作成 vs
inplace=True:inplace=False(デフォルト)は新しいDataFrameオブジェクトを作成するため、元のDataFrameと同じくらいのメモリを消費します。非常に大きなDataFrameの場合、メモリ不足の原因になる可能性があります。一方、inplace=Trueは元のDataFrameを直接変更するため、メモリ効率が良い場合があります。ただし、前述のようにinplace=Trueには他のデメリットもあるため、パフォーマンスがボトルネックになる場合にのみ検討するのが良いでしょう。 - コピーの発生: Pandasの操作は、意図せずデータのコピーを生成することがあります。
dropnaメソッド自体は、通常は新しいオブジェクトを作成するか、元のオブジェクトを直接変更するため、中間的なコピーの発生は少ないですが、前後の処理(例えば、特定の条件でのスライス後のdropnaなど)との組み合わせによっては注意が必要です。SettingWithCopyWarningなどが表示された場合は、データのコピーが発生している可能性があり、意図した通りにデータが変更されないリスクがあることを示唆しています。
多くの場合、数百万行程度のデータセットであれば、デフォルトのinplace=Falseでパフォーマンス上の大きな問題は発生しないでしょう。数千万行を超えるような非常に大規模なデータセットを扱う場合は、メモリ使用量や処理時間を考慮してinplace=Trueの利用や、より高速な処理が可能なライブラリ(例: Dask)の検討が必要になるかもしれません。
まとめ
この記事では、Pandasにおける欠損値処理の基本であるdropnaメソッドについて、詳細に解説しました。
dropnaメソッドは、データ分析の前処理において、欠損値を含む行や列を削除するために使用される非常に重要なツールです。その挙動は、以下の主要なパラメータによって制御されます。
axis: 削除の対象を「行」('index') にするか「列」('columns') にするかを指定します。(デフォルトは'index')how: 削除の条件として、「1つでも」欠損値があれば削除 ('any') するか、「全てが」欠損値の場合にのみ削除 ('all') するかを指定します。(デフォルトは'any')thresh: 削除の条件として、指定された軸(行または列)における「非欠損値の最小数」を指定します。この数以上の非欠損値があれば保持され、そうでなければ削除されます。subset: 欠損値の判定を行う対象を、DataFrameの特定の「列」(axis='index'の場合)または特定の「行」(axis='columns'の場合)に限定するために使用します。
これらのパラメータを適切に組み合わせることで、データの特性や分析の目的に応じた柔軟な欠損値処理が可能になります。
dropnaはシンプルで使いやすい反面、データの削除による情報損失やバイアスの導入といったリスクも伴います。そのため、dropnaを使用する前には、データセットの欠損値の量、分布、そして欠損が発生したメカニズムを十分に理解することが非常に重要です。isnull().sum(), info(), 可視化ツールなどを活用して、欠損値の状況を把握しましょう。
また、dropnaが常に最善の選択肢とは限らないことを理解し、fillnaによる補完や、欠損値を直接扱える分析手法といった代替手段も視野に入れることが、データ分析における欠損値処理においては不可欠です。
本記事で学んだdropnaの知識を活かして、あなたのデータ分析の質を向上させてください。欠損値処理はデータ分析の成功を左右する重要なステップです。この記事が、そのステップを自信を持って進むための一助となれば幸いです。
今後の学習として、fillnaメソッドの使い方や、より高度な補間技術、あるいは欠損値を考慮したモデリング手法などについて学ぶことで、データ分析における欠損値処理の引き出しをさらに増やすことができるでしょう。