σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft Academyロードマップ
Ctrl K
AO
+1.2k 人の学習者
ログイン参加する ↗
ロードマップ
AO
+1.2k 人の学習者
ログイン
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python ロードマップ›モジュール 2 · 市場データのためのNumPyとpandas›トピック 2.15·~40 分中級

欠損データの扱い

ロードマップ上の位置モジュール 2/11 · トピック 15/20
2.15 欠損データの扱い
—ラボスコア— / 100合計XP—連続日数—

データの欠損

実際の価格表には欠損があります。その日は取引がなかった、フィードが気配を落とした、市場が休日だった、などです。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 down
過去で埋め、未来では埋めないbfill() と 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点
エクスプローラー · lab-48-1
▾ lab-48-1
…
bash · ブラウザ内
learner@quantcraft:~/lab-48-1$
Pythonは未読み込みPythonLn 1, Col 1Spaces: 4UTF-8

このトピックをリセットしますか?

lab-48-1 の内容はすべてスターターファイルに戻り、ターミナルも消去されます。現在の解答は失われ、元に戻せません。

リセットされるもの
  • main.py → スターターコード
  • ターミナル履歴とテスト出力
  • 未提出の変更
保持されるもの
  • ラボの最高スコア
  • XPと連続日数
  • 講義の進捗とメモ

コードを実行するにはログインしてください。

レクチャーはすべて無料で読めます。コードの実行、テスト、ラボの提出にはアカウントの作成かログインが必要です。

  • ブラウザ内IDEとbashターミナル。インストール不要
  • 即時採点、XP、ロードマップでの現在地
アカウントを作成ログイン
または
GitHubで続ける