CSVを読み込んだとき、表の中に NaN があると「とりあえず0で埋めればよいのか」と迷うことがあります。
fillna()は、行を削除せずに欠損値を指定した値で埋めるpandasのメソッドです。ただし、売上が未記録であることと、売上が0円だったことは同じではありません。
先に結論を示すと、次の順序で考えるのが基本です。
- 欠損が本当に0を意味するか
- 数値列かカテゴリ列か
- 数値列は外れ値の影響を受けやすいか
- 時系列では直前の状態を引き継いでよいか
- 補完後の欠損数・集計値・データ型に問題がないか
この記事では、店舗の売上データを最初から最後まで使い、欠損値を「見つける→意味を考える→補う→結果を確認する」流れをGoogle Colabで再現します。
この記事でわかること
fillna()の基本的な使い方- 0・平均値・中央値・最頻値・前の値の選び方
- 特定列・列ごとに異なる値を補完する方法
- 欠損処理の使い分けと、補完後の確認方法
fillna()は、データ分析の流れでは前処理に位置します。CSVを読み込み、head()やデータ型を確認し、欠損値を調べたあとで使います。
fillna()で何を入れるかを先に決める
同じ NaN でも意味は列によって異なります。コードを書く前に、次の表で補完方法を選びます。
| 補完方法 | 向いている列・状況 | 選ぶ目安 | 注意点 |
|---|---|---|---|
| 0 | 件数や金額が「発生なし」を表す列 | 欠損が本当に0と判断できる | 未記録を0にすると集計値が変わる |
| 平均値 | 外れ値が少ない数値列 | 値がおおむね均等に分布している | 外れ値に引っ張られやすい |
| 中央値 | 外れ値がある数値列 | 並べた中央の値を代表値にしたい | 元の分布を完全に再現するわけではない |
| 最頻値 | 商品名や区分などのカテゴリ列 | 最も多い値で補う根拠がある | 少数カテゴリを過小評価することがある |
| 固定文字列 | カテゴリ列で欠損自体を区別したい | 「不明」「未回答」として残したい | 実際の商品名などと混同しない名称にする |
| 前の値 | 時系列で状態が継続すると考えられる列 | 直前の記録を引き継いでよい | 日付順でなければ誤った値を使う |
迷ったときは、機械的に fillna(0) を実行せず、列の意味を確認してください。
欠損値を含む店舗データを用意する
日付・商品名・売上・来店者数を含む小さなDataFrameを作ります。None や np.nan は、DataFrameでは欠損値として扱われます。
import numpy as np
import pandas as pd
df = pd.DataFrame({
"日付": pd.to_datetime([
"2026-08-01", "2026-08-02", "2026-08-03",
"2026-08-04", "2026-08-05", "2026-08-06"
]),
"商品名": ["コーヒー", None, "紅茶", "コーヒー", "紅茶", None],
"売上": [12000, np.nan, 15000, 100000, np.nan, 18000],
"来店者数": [30, np.nan, 42, 50, np.nan, 45]
})
df
| 日付 | 商品名 | 売上 | 来店者数 | |
|---|---|---|---|---|
| 0 | 2026-08-01 | コーヒー | 12000.0 | 30.0 |
| 1 | 2026-08-02 | None | NaN | NaN |
| 2 | 2026-08-03 | 紅茶 | 15000.0 | 42.0 |
| 3 | 2026-08-04 | コーヒー | 100000.0 | 50.0 |
| 4 | 2026-08-05 | 紅茶 | NaN | NaN |
| 5 | 2026-08-06 | None | 18000.0 | 45.0 |
商品名・売上・来店者数に NaN があります。一方、100000 は大きな売上ですが、意味のある観測値なので欠損値やエラー値として扱いません。
次に、列ごとの欠損数を確認します。isnull()は各要素が欠損しているかをTrue/Falseで返し、続けて sum() を使うとTrueの数、つまり欠損数を数えられます。
missing_before = df.isnull().sum().rename("補完前")
missing_before
| 補完前 | |
|---|---|
| 日付 | 0 |
| 商品名 | 2 |
| 売上 | 2 |
| 来店者数 | 2 |
日付は0件、残りの3列はそれぞれ2件欠損しています。isnull()は欠損を見つける処理であり、fillna()は見つけた欠損を埋める処理です。
fillna()の基本:固定値で欠損値を埋める
基本形は 対象.fillna(埋める値) です。まずはコピーを使い、商品名の欠損だけを「不明」で埋めます。
df_fixed = df.copy()
df_fixed["商品名"] = df_fixed["商品名"].fillna("不明")
df_fixed[["日付", "商品名"]]
| 日付 | 商品名 | |
|---|---|---|
| 0 | 2026-08-01 | コーヒー |
| 1 | 2026-08-02 | 不明 |
| 2 | 2026-08-03 | 紅茶 |
| 3 | 2026-08-04 | コーヒー |
| 4 | 2026-08-05 | 紅茶 |
| 5 | 2026-08-06 | 不明 |
元の6行を残したまま、2行目と6行目の商品名だけが「不明」になりました。特定列を指定しているため、売上と来店者数の NaN はそのままです。
df.fillna(0) のようにDataFrame全体を0で埋めることもできますが、商品名まで数値の0になるため、このデータには適しません。列ごとに意味を考えることが大切です。
数値列を平均値・中央値・0で埋める
平均値と中央値を確認してから選ぶ
売上の観測値には10万円という大きな値があります。補完方法を決める前に、平均値と中央値を比較します。
sales_mean = df["売上"].mean()
sales_median = df["売上"].median()
pd.Series({
"平均値": sales_mean,
"中央値": sales_median
}, name="売上")
| 売上 | |
|---|---|
| 平均値 | 36250.0 |
| 中央値 | 16500.0 |
平均値は36,250円、中央値は16,500円です。平均値は10万円の影響を受けています。この例では、通常の売上水準に近い中央値を補完値として選びます。
これは「外れ値があれば必ず中央値」という意味ではありません。10万円が通常のキャンペーン売上であれば、その情報を踏まえて判断する必要があります。
売上を中央値で補完する
特定列の欠損値を中央値で埋め、同じ列へ代入します。
df_median = df.copy()
df_median["売上"] = df_median["売上"].fillna(df_median["売上"].median())
df_median[["日付", "売上"]]
| 日付 | 売上 | |
|---|---|---|
| 0 | 2026-08-01 | 12000.0 |
| 1 | 2026-08-02 | 16500.0 |
| 2 | 2026-08-03 | 15000.0 |
| 3 | 2026-08-04 | 100000.0 |
| 4 | 2026-08-05 | 16500.0 |
| 5 | 2026-08-06 | 18000.0 |
2行目と5行目の売上が16,500円になりました。mean()やmedian()は標準では欠損値を除いて計算します。また、補完値は推定値であり、本当の売上が判明したわけではありません。
0埋めは「発生なし」を意味するときだけ使う
来店者数の欠損が「休業日で来店者が0人だった」と確認できる場合は、fillna(0)が候補になります。
df_zero = df.copy()
df_zero["来店者数"] = df_zero["来店者数"].fillna(0)
df_zero[["日付", "来店者数"]]
| 日付 | 来店者数 | |
|---|---|---|
| 0 | 2026-08-01 | 30.0 |
| 1 | 2026-08-02 | 0.0 |
| 2 | 2026-08-03 | 42.0 |
| 3 | 2026-08-04 | 50.0 |
| 4 | 2026-08-05 | 0.0 |
| 5 | 2026-08-06 | 45.0 |
欠損していた2日が0人になりました。ただし、単なる記録漏れなら0埋めは不適切です。欠損値は「分からない」、0は「存在しない・発生しない」という別の意味を持ちます。
カテゴリ列を最頻値で埋める
商品名のようなカテゴリ列では、mode()で最頻値を求められます。mode()はSeriesを返すため、最初の値を [0] で取り出します。
product_mode = df["商品名"].mode()[0]
df_mode = df.copy()
df_mode["商品名"] = df_mode["商品名"].fillna(product_mode)
print("最頻値:", product_mode)
df_mode[["日付", "商品名"]]
最頻値: コーヒー
| 日付 | 商品名 | |
|---|---|---|
| 0 | 2026-08-01 | コーヒー |
| 1 | 2026-08-02 | コーヒー |
| 2 | 2026-08-03 | 紅茶 |
| 3 | 2026-08-04 | コーヒー |
| 4 | 2026-08-05 | 紅茶 |
| 5 | 2026-08-06 | コーヒー |
このデータでは「コーヒー」と「紅茶」が同数です。mode()には両方が含まれますが、[0]を指定すると並び順で最初の「コーヒー」が選ばれます。
つまり、最頻値が同数の場合に [0] を使うと、一方を機械的に選ぶことになります。根拠なく商品名を推定したくない場合は、最頻値より「不明」という固定文字列の方が誤解を避けられます。これはコードは動くが、結果の意味を誤解しやすい場面です。
列ごとに異なる値で一度に補完する
fillna()へ辞書を渡すと、列ごとに異なる補完値を指定できます。ここでは商品名を「不明」、売上を中央値、来店者数を0で補います。
fill_values = {
"商品名": "不明",
"売上": df["売上"].median(),
"来店者数": 0
}
df_filled = df.fillna(fill_values)
df_filled
| 日付 | 商品名 | 売上 | 来店者数 | |
|---|---|---|---|---|
| 0 | 2026-08-01 | コーヒー | 12000.0 | 30.0 |
| 1 | 2026-08-02 | 不明 | 16500.0 | 0.0 |
| 2 | 2026-08-03 | 紅茶 | 15000.0 | 42.0 |
| 3 | 2026-08-04 | コーヒー | 100000.0 | 50.0 |
| 4 | 2026-08-05 | 紅茶 | 16500.0 | 0.0 |
| 5 | 2026-08-06 | 不明 | 18000.0 | 45.0 |
同じ行と列を保ったまま、各列の意味に合わせて欠損値が置き換わりました。実際のデータでは、来店者数の欠損が本当に0を意味することを確認してから使います。
補完前後を同じ表で確認する
処理後は、見た目だけでなく欠損数を再計算します。補完前と補完後を横に並べると、どの列が変わったかを追えます。
missing_after = df_filled.isnull().sum().rename("補完後")
missing_comparison = pd.concat([missing_before, missing_after], axis=1)
missing_comparison
| 補完前 | 補完後 | |
|---|---|---|
| 日付 | 0 | 0 |
| 商品名 | 2 | 0 |
| 売上 | 2 | 0 |
| 来店者数 | 2 | 0 |
商品名・売上・来店者数の欠損数が2件から0件になりました。日付はもともと0件なので変化していません。
値そのものも処理前後で比較します。接尾辞を付けることで、同じ列名を混同せず確認できます。
before_after = df.add_suffix("_補完前").join(df_filled.add_suffix("_補完後"))
before_after[[
"日付_補完前",
"商品名_補完前", "商品名_補完後",
"売上_補完前", "売上_補完後",
"来店者数_補完前", "来店者数_補完後"
]]
| 日付_補完前 | 商品名_補完前 | 商品名_補完後 | 売上_補完前 | 売上_補完後 | 来店者数_補完前 | 来店者数_補完後 | |
|---|---|---|---|---|---|---|---|
| 0 | 2026-08-01 | コーヒー | コーヒー | 12000.0 | 12000.0 | 30.0 | 30.0 |
| 1 | 2026-08-02 | None | 不明 | NaN | 16500.0 | NaN | 0.0 |
| 2 | 2026-08-03 | 紅茶 | 紅茶 | 15000.0 | 15000.0 | 42.0 | 42.0 |
| 3 | 2026-08-04 | コーヒー | コーヒー | 100000.0 | 100000.0 | 50.0 | 50.0 |
| 4 | 2026-08-05 | 紅茶 | 紅茶 | NaN | 16500.0 | NaN | 0.0 |
| 5 | 2026-08-06 | None | 不明 | 18000.0 | 18000.0 | 45.0 | 45.0 |
2行目と5行目では売上が16,500円、来店者数が0になり、2行目と6行目の商品名が「不明」になっています。欠損していなかった値は変更されていません。
自分のデータへ置き換えるときは、次の3点を確認してください。
- 補完対象ではない列が変わっていないか
- 欠損数が想定どおり減ったか
- 補完値が列の意味と矛盾していないか
前の値で埋めるなら日付順を確認してffill()を使う
在庫区分や設定値など、直前の状態が次の記録まで続く列では前方補完が使えます。ここでは、入荷記録がある日だけ入力された在庫区分を例にします。前方補完の前に、必ず日付順へ並べます。
df_status = pd.DataFrame({
"日付": pd.to_datetime([
"2026-08-01", "2026-08-02", "2026-08-03",
"2026-08-04", "2026-08-05"
]),
"在庫区分": ["通常", None, None, "残りわずか", None]
})
df_status = df_status.sort_values("日付").copy()
df_status["在庫区分_前方補完"] = df_status["在庫区分"].ffill()
df_status
| 日付 | 在庫区分 | 在庫区分_前方補完 | |
|---|---|---|---|
| 0 | 2026-08-01 | 通常 | 通常 |
| 1 | 2026-08-02 | None | 通常 |
| 2 | 2026-08-03 | None | 通常 |
| 3 | 2026-08-04 | 残りわずか | 残りわずか |
| 4 | 2026-08-05 | None | 残りわずか |
8月2日・3日は直前の「通常」、8月5日は直前の「残りわずか」で補完されます。この例では、次の変更記録まで同じ状態が続くという前提があります。
一方、来店者数や売上のように毎日変わる値は、前日の値をそのまま引き継ぐ根拠が弱い場合があります。また、並び順が正しくなければ「前の行」は「前日」になりません。ffill()は、直前の状態が継続すると説明できる列に限定します。
過去では fillna(method="ffill") という書き方も使われましたが、現在は ffill()を直接使う方が分かりやすく、将来のpandasでも扱いやすい書き方です。
fillna()・isnull()・dropna()の違い
欠損値に関係する3つのメソッドは役割が異なります。
| 機能名 | 何をするか | 選ぶ場面 | 注意点 |
|---|---|---|---|
isnull() |
欠損かどうかをTrue/Falseで確認する | 欠損の場所や件数を調べる | 値は変更しない |
fillna() |
欠損値を指定した値で埋める | 行を残して分析したい | 補完値の根拠が必要 |
dropna() |
欠損を含む行・列を削除する | 削除しても分析への影響が小さい | データ数が減る |
ffill() |
欠損を直前の有効値で埋める | 状態が継続する時系列 | 並び順と継続性を確認する |
基本の流れは、isnull()で確認してから、行を残すならfillna()、削除するならdropna()を選ぶことです。
コードは動くが結果を誤解しやすい例
売上を0で埋めると平均値が下がる
欠損している売上を0円とみなすと、コードは問題なく動きます。しかし、「未記録」を「売上なし」に変更するため、平均値の意味が変わります。
average_comparison = pd.Series({
"補完前(欠損を除外)": df["売上"].mean(),
"0で補完後": df["売上"].fillna(0).mean(),
"中央値で補完後": df["売上"].fillna(df["売上"].median()).mean()
}, name="平均売上")
average_comparison
| 平均売上 | |
|---|---|
| 補完前(欠損を除外) | 36250.000000 |
| 0で補完後 | 24166.666667 |
| 中央値で補完後 | 29666.666667 |
補完前の平均売上は36,250円、0で補完すると約24,166.67円、中央値で補完すると約29,666.67円です。
どの値が正解かは、欠損が発生した理由によって決まります。ここで重要なのは、補完すると集計結果も変わると理解し、処理前後を比較することです。中央値で埋めても元の分布を完全に復元できるわけではありません。
fillna()の結果を代入しないと元のDataFrameは変わらない
fillna()は、通常は補完後の新しいDataFrameやSeriesを返します。次のように結果を表示するだけでは、元の df は更新されません。
preview = df["売上"].fillna(0)
print("補完結果の欠損数:", preview.isnull().sum())
print("元の列の欠損数:", df["売上"].isnull().sum())
補完結果の欠損数: 0
元の列の欠損数: 2
補完結果の欠損数は0件ですが、元の列には2件残っています。元データを更新したい場合は、df["売上"] = df["売上"].fillna(0)のように代入します。
inplace=Trueを使える場面もありますが、対象が分かりやすく処理を追いやすい代入形式を、この記事では基本形として使います。
補完後はデータ型も確認する
整数に見える列でも、欠損値を含むため小数型になっていることがあります。補完しただけで、必ず希望する整数型になるとは限りません。
dtype_comparison = pd.DataFrame({
"補完前": df.dtypes.astype(str),
"補完後": df_filled.dtypes.astype(str)
})
dtype_comparison
| 補完前 | 補完後 | |
|---|---|---|
| 日付 | datetime64[ns] | datetime64[ns] |
| 商品名 | object | object |
| 売上 | float64 | float64 |
| 来店者数 | float64 | float64 |
この実行環境では、来店者数は補完後も float64です。整数として扱いたい場合は、値を確認したうえでnullable整数型の Int64などへ変換します。pandasのバージョンや元のデータ型によって型推論が異なる可能性があるため、dtypesで実際の結果を確認してください。
df_integer = df_filled.copy()
df_integer["来店者数"] = df_integer["来店者数"].astype("Int64")
df_integer[["日付", "来店者数"]].dtypes
| 0 | |
|---|---|
| 日付 | datetime64[ns] |
| 来店者数 | Int64 |
データ分析では「確認→判断→補完→再確認」の順で使う
fillna()だけを単独で実行するのではなく、前後の工程を含めて考えます。
- CSVを読み込み、
head()で実データを確認する dtypesやto_numeric()で列の型を確認・変換するisnull().sum()で列ごとの欠損数を確認する- 列の意味と
describe()・value_counts()の結果から補完方法を選ぶ - 行を残すなら
fillna()、削除するならdropna()を使う - もう一度、欠損数・集計値・データ型を確認する
- 整えたデータを抽出・集計・可視化へ進める
この順序なら、「コードは動いたが、意味の違う値で埋めてしまった」という失敗を減らせます。
まとめ:fillna()は欠損の意味を考えてから使う
fillna()は、行を残したまま欠損値を補うメソッド- 欠損が「発生なし」を意味すると確認できる場合だけ0埋めを選ぶ
- 数値列は分布を確認して平均値か中央値、カテゴリ列は固定文字列か最頻値を検討する
ffill()は、日付順に並び、直前の状態を引き継げる時系列だけに使う- 補完後は欠損数・集計結果・データ型を確認する
最終的なゴールは、NaNを機械的に0へ置き換えることではありません。列の意味とデータ分布に合った方法を選び、その影響まで確認することです。
関連記事
- 欠損値を可視化して攻略!Pandas isnullとヒートマップ活用術
- Pandas dropna()・drop_duplicates()の使い方|欠損・重複の削除とdrop()基本
- pandas describe()の使い方|統計量の意味と結果の見方を解説
- pandas astype()の使い方|文字列・整数・小数の型変換とエラー対処
- Google ColabでCSVを読み込む方法|アップロード・Drive・read_csv
CSV読込→欠損確認→補完または削除→集計という順に読むと、前処理の流れをつかみやすくなります。
fillna()とdropna()の違いは何ですか?
fillna()は欠損値を別の値で埋めて行を残し、dropna()は欠損を含む行や列を削除します。削除によって必要なデータまで失う場合は補完を検討し、補完値に合理的な根拠がない場合は削除や未処理のままにする選択肢も検討します。
NaNはすべて0で埋めてもよいですか?
いいえ。NaNは「値が分からない」、0は「発生していない」という異なる意味を持ちます。休業日の来店者数など、欠損が本当に0を意味すると確認できる場合に限って0埋めを選びます。
平均値と中央値はどちらを使いますか?
外れ値が少なく値が均等に分布していれば平均値、極端な値の影響を抑えたい場合は中央値が候補です。ただし、どちらも推定値なので、列の意味と欠損理由を優先して決めます。
前の値で埋めるにはどうしますか?
対象列にffill()を使います。実行前に日付順へ並べ、直前の値が次の時点にも継続すると説明できる列か確認してください。
fillna()を実行しても元のDataFrameが変わらないのはなぜですか
fillna()は通常、補完後の新しいオブジェクトを返すためです。元の列を更新するなら、df["列名"] = df["列名"].fillna(値)のように結果を代入します。
列ごとに異なる値を指定できますか?
はい。fillna()へ列名と補完値を対応させた辞書を渡せます。商品名は「不明」、売上は中央値というように、列ごとに意味の異なる補完が可能です。
fillna()でデータ型が変わることはありますか?
補完値・元のデータ型・pandasのバージョンによって結果の型が変わる可能性があります。補完後にdtypesを確認し、必要ならastype()で明示的に変換します。
欠損値が埋まったか確認するにはどうしますか?
補完後のDataFrameにisnull().sum()を実行します。欠損数だけでなく、補完対象外の列、平均値などの集計結果、データ型も処理前と比較してください。
コメント