データの欠損
実際の価格表には欠損があります。その日は取引がなかった、フィードが気配を落とした、市場が休日だった、などです。pandas はそれを NaN("not a number")で表します。対処を決める前にまず見ましょう。df.isna() は真偽値の表を返し、.sum() は列ごとの欠損数、.mean() はその割合を返します。40%が空の列と、1つだけ欠けた列とでは問題の性質が違います。
import pandas as pd
nan = float("nan")
df = pd.DataFrame(
{"AAPL": [184.0, nan, 186.5, 187.0, nan], "MSFT": [410.0, 411.0, nan, nan, 415.0]},
index=pd.bdate_range("2026-03-02", periods=5),
)
print(df.isna().sum().to_dict()) # {'AAPL': 2, 'MSFT': 2} holes per column
print(df.isna().mean().round(2).to_dict()) # {'AAPL': 0.4, 'MSFT': 0.4} share of holes引き継ぐか、捨てるか
価格の自然な補完は直近の既知の価格です。新しい気配が来るまで、市場は古い水準のままだからです。これが ffill()(前方補完)です。長い欠損はデータが古いことを意味しがちなので、ffill(limit=2) は最大でも連続2日までを補います。先頭の NaN は引き継ぐ前の価格がないので NaN のままです。もう一つの方法は削除で、dropna() は欠損のある行をすべて除き、dropna(subset=["AAPL"]) は必要な列だけを見ます。削除は安全ですが、データが減り、日付の間隔も変わります。
msft = df["MSFT"]
print(msft.ffill().tolist()) # [410.0, 411.0, 411.0, 411.0, 415.0]
print(msft.ffill(limit=1).tolist()) # [410.0, 411.0, 411.0, nan, 415.0] only one day carried
print(len(df.dropna())) # 1 only one row is complete
print(len(df.dropna(subset=["AAPL"]))) # 3 rows where AAPL is known補間と、決して埋めてはいけないもの
interpolate() は欠損を、両側の既知の値を結ぶ直線で埋めます。滑らかな量(利回り曲線、ボラティリティ水準)には向きますが、跳ねる価格にはあまり向きません。端には注意してください。既定では interpolate() は末尾の NaN にも最後の値をコピーしてしまい、データを作り出します。limit_area="inside" で2つの既知の値の間だけに限定できます。盲目的に 0 で埋めてはいけません。価格0は暴落、リターン0は横ばいの日であり、間違った表への fillna(0) は以後に計算するすべての平均を黙って変えます。
gappy = pd.Series([nan, nan, 10.0, nan, nan, 16.0, nan])
print(gappy.interpolate().tolist())
# [nan, nan, 10.0, 12.0, 14.0, 16.0, 16.0] the last 16.0 is invented
print(gappy.interpolate(limit_area="inside").tolist())
# [nan, nan, 10.0, 12.0, 14.0, 16.0, nan] only between known values# Filling with 0 is a decision, not a default: it changes the statistics
print(gappy.fillna(0).tolist()) # [0.0, 0.0, 10.0, 0.0, 0.0, 16.0, 0.0]
print(gappy.mean()) # 13.0 NaN are skipped
print(gappy.fillna(0).mean()) # 3.7142857142857144 the zeros drag the average downbfill() と interpolate() は欠損の後の値を使うため、欠損の日付の時点でまだ存在しない情報を使うことになります。バックテストでは先読みバイアスです。戦略がリアルタイムで見るものには ffill() を使い、補間は全体として分析する過去データの整形にとどめましょう。df.isna().sum()は列ごとの欠損数。扱いを決める前にデータを見るffill(limit=n)は直近の既知の値を引き継ぐ(価格には正解)。dropna(subset=...)は不完全な行を除くinterpolate(limit_area="inside")は内部の欠損だけを埋める。習慣で0や未来を使って埋めない
次は外れ値の検出です。存在するのに正しいはずのない値を見つけます。
ラボ:採点される内容
count_missingで各列の欠損を数える25点forward_fillで直近の価格を引き継ぐ25点complete_rowsで信頼できる行だけを残す25点fill_gapsでデータ内部の欠損を埋める25点