【Rプログラミング】欠損値補間もこれで完璧!主要な補間関数と代替メソッド
具体的には、以下のような状況で補間が利用されます。
- 関数の近似
実験データなどから、その背後にある関数を近似的に表現したい場合に用いられます。 - データの間隔を細かくしたい場合
測定されたデータ点の間をより滑らかな曲線で結び、より細かい間隔での値を求めたい場合に利用されます。 - データに欠損がある場合
特定の期間のデータが記録されていない場合、既存のデータからその期間の値を推測するために補間が使われます。
Rには、様々な補間手法を実装した関数やパッケージが存在します。代表的なものとしては、以下のようなものが挙げられます。
代表的な補間関数と手法
-
- 最もシンプルで基本的な補間方法です。
- 既知の2つのデータ点を直線で結び、その直線上にある中間点の値を推定します。
approx(x, y, xout)
のように使用し、x
とy
が既知のデータ点、xout
が補間したいX座標の値を指定します。- 特徴
計算が高速でシンプルですが、補間された曲線は滑らかではなく、カクカクした印象になることがあります。
-
spline()
関数: スプライン補間 (Spline Interpolation)- より滑らかな補間曲線を作成するために用いられます。
- 各データ区間を低次の多項式(通常は3次多項式、Cubic Spline)で結びつけ、各データ点での接続が滑らかになるように制約を設けます。
spline(x, y, xout)
のように使用します。- 特徴
線形補間よりもはるかに滑らかな補間結果が得られます。データ点の間に急激な変化がある場合でも、比較的自然な曲線を描くことができます。 - 特に、
splinefun()
関数は、補間関数自体を返すため、様々なx
の値に対して繰り返し補間を行う場合に便利です。
-
他のパッケージの利用 Rの標準パッケージ以外にも、より高度な補間機能を提供するパッケージがあります。
- akima パッケージ
2次元の補間(グリッドデータではない不規則なデータ点からの補間)に特化しています。interp()
関数などが提供されます。 - zoo パッケージ
時系列データの欠損値補間に非常に強力な機能を提供します。na.approx()
(線形補間)、na.spline()
(スプライン補間)などがあります。
- akima パッケージ
- 補間方法の選択
どのような補間方法を選ぶかは、データの性質や目的によって異なります。単純な線形補間で十分な場合もあれば、滑らかさが必要な場合はスプライン補間が適していることもあります。 - 外挿 (Extrapolation) への注意
補間は既知のデータ点間の値を推定するものであり、データ範囲外の値を推定する「外挿」には注意が必要です。外挿は、データにない領域を予測することになるため、補間よりも精度が低く、信頼性が保証されません。
データの並び順に関するエラー
- トラブルシューティング
データを補間する前に、x
をソートしてください。# エラーが発生する例 x_unsorted <- c(3, 1, 4, 2) y <- c(30, 10, 40, 20) # approx(x_unsorted, y, xout = 2.5) # エラーになる # 解決策:xでソートする df <- data.frame(x = x_unsorted, y = y) df_sorted <- df[order(df$x), ] approx(df_sorted$x, df_sorted$y, xout = 2.5)
- エラーメッセージの例
Error in approx(x, y, xout = xout) : 'x' must be sorted non-decreasingly
- 問題
x
引数(独立変数)がソートされていないとエラーが発生することがあります。特にapprox()
やspline()
関数は、x
が昇順にソートされていることを前提としています。
データにNA(欠損値)が含まれている場合
- トラブルシューティング
- na.omit()やcomplete.cases()でNAを含む行を削除する
欠損値を含むデータ点を補間に含めない場合は、これらが最も簡単な方法です。x <- c(1, 2, NA, 4, 5) y <- c(10, 20, 30, NA, 50) # NAを含む行を削除 df <- data.frame(x = x, y = y) df_clean <- na.omit(df) approx(df_clean$x, df_clean$y, xout = 3)
- na.approx()やna.spline()(zooパッケージ)を使用する
欠損値自体を補間したい場合は、zoo
パッケージの関数が非常に便利です。これらは、内部的に欠損値を補間してから、元の補間を行うことができます。library(zoo) x <- c(1, 2, NA, 4, 5) y <- c(10, 20, NA, 40, 50) # na.approx()でNAを線形補間 y_interpolated_na <- na.approx(y, x) print(y_interpolated_na) # 30のNAが補間される # xoutの指定 approx(x, y_interpolated_na, xout = 3)
- 欠損値の多いデータ
あまりにも多くの欠損値がある場合、補間の結果は信頼性が低くなります。その場合は、補間以外の欠損値処理(例: 平均値や中央値で置換、より高度な多重代入法など)を検討する必要があるかもしれません。
- na.omit()やcomplete.cases()でNAを含む行を削除する
- エラーメッセージの例
Error in approx(x, y, xout = xout) : 'x' and 'y' must not contain NA values
- 問題
補間関数は、通常、入力データにNA
が含まれていると正しく動作しません。
外挿 (Extrapolation) に関する問題
- トラブルシューティング
- rule引数の使用 (approx())
approx()
関数では、rule
引数を使って外挿時の挙動を制御できます。rule = 1
(デフォルト): 範囲外の値に対してNA
を返す。rule = 2
: 範囲外の値に対して、最も近い端点の値をそのまま使用する。yleft
とyright
引数: 範囲外の値を特定の値で埋めることもできます。 <!-- end list -->
# rule = 2 を使用して外挿 approx(x, y, xout = 0.5, rule = 2) # 10 を返す approx(x, y, xout = 3.5, rule = 2) # 30 を返す # yleft/yright を使用 approx(x, y, xout = 0.5, yleft = 0) # 0 を返す
- 外挿の限界を理解する
外挿は、既知のデータパターンが範囲外でも継続すると仮定することに他なりません。この仮定が正しい保証はないため、外挿結果の信頼性は低くなります。本当に外挿が必要な場合は、補間ではなく、回帰分析などのモデリング手法を検討する方が適切です。
- rule引数の使用 (approx())
- エラーメッセージの例 (直接的なエラーではないが、注意が必要なケース)
approx()
やspline()
では、xout
がx
の範囲外の場合、デフォルトではNA
を返します。x <- c(1, 2, 3) y <- c(10, 20, 30) # 範囲外の値を補間しようとする (外挿) approx(x, y, xout = 0.5) # NAを返す approx(x, y, xout = 3.5) # NAを返す
- 問題
多くの補間関数は、既知のデータ点の範囲外の値を推定(外挿)しようとすると、NA
を返すか、期待しない結果を生成します。
十分なデータ点がない場合
- トラブルシューティング
- データ点の確認
補間を実行する前に、x
とy
に十分なデータ点があることを確認してください。 - 単純な補間方法の検討
データ点が少ない場合は、より単純な線形補間(approx()
)を検討してください。
- データ点の確認
- エラーメッセージの例
Error in splinefun(x, y, method = "fmm") : 'x' must have at least 4 unique points
- 問題
補間を実行するために必要なデータ点が不足している場合、エラーが発生します。例えば、spline()
関数は少なくとも4つのデータ点がないと、自然スプライン補間ができません。approx()
でも、少なくとも2つのデータ点が必要です。
xの値が重複している場合
- トラブルシューティング
- 重複値の除去
unique()
関数などを使用して、重複するx
の値を削除するか、重複するx
を持つy
の値の平均をとるなどの処理を検討します。 - データの集約
例えば、同じx
に対して複数のy
がある場合、それらを平均するなどして1つのデータ点に集約することを検討します。
- 重複値の除去
- エラーメッセージの例
Error in splinefun(x, y, method = "fmm") : 'x' values must be strictly increasing or strictly decreasing
(実際にはx
のソートの問題と類似) - 問題
x
の値が重複している場合、補間関数が正しく動作しないことがあります。特に、spline()
のような高次の補間では問題が生じやすいです。
- トラブルシューティング
- 結果のプロット
補間した結果を必ず元のデータ点と一緒にプロットし、視覚的に確認することが重要です。x <- c(1, 2, 3, 4, 5) y <- c(1, 4, 1, 4, 1) # ルンゲ現象を発生しやすいデータ # 線形補間 app_res <- approx(x, y, xout = seq(1, 5, 0.1)) # スプライン補間 spl_res <- spline(x, y, xout = seq(1, 5, 0.1)) plot(x, y, pch = 16, cex = 1.5, main = "Interpolation Comparison") lines(app_res, col = "blue", lty = 2) # 線形補間 lines(spl_res, col = "red") # スプライン補間 legend("topright", legend = c("Original", "Linear", "Spline"), col = c("black", "blue", "red"), pch = c(16, NA, NA), lty = c(NA, 2, 1))
- 補間方法の再検討
結果が不自然な場合は、別の補間方法を試したり、データの性質により適したより高度な補間アルゴリズム(例: ロバストなスプラインなど)を提供するパッケージを検討したりしてください。
- 結果のプロット
- 問題
エラーではないですが、補間結果が視覚的に期待と異なることがあります。特に、スプライン補間では、データ点の間に不自然な波打ち(ルンゲ現象など)が発生することがあります。
approx() 関数 (線形補間)
approx()
は、最も基本的な線形補間を実行します。既知のデータ点を直線で結び、その直線上の値を推定します。
# 1. 基本的な線形補間
# 既知のデータ点
x_known <- c(1, 3, 5, 7, 9)
y_known <- c(10, 25, 30, 45, 50)
# 補間したいX座標
x_interpolate <- c(2, 4, 6, 8)
# approx() を使って補間
# 結果は$xと$yのリストとして返される
result_approx <- approx(x_known, y_known, xout = x_interpolate)
print("--- approx() による線形補間結果 ---")
print(result_approx)
# 結果の可視化
plot(x_known, y_known,
main = "線形補間 (approx())",
xlab = "X", ylab = "Y",
pch = 16, col = "blue", cex = 1.5,
xlim = range(c(x_known, x_interpolate)),
ylim = range(c(y_known, result_approx$y)))
# 補間された点をプロット
points(result_approx$x, result_approx$y, pch = 4, col = "red", cex = 1.5)
# 補間された点を線で結ぶ
lines(result_approx, col = "red", lty = 2) # lty=2 で破線
# 元の点を線で結ぶ
lines(x_known, y_known, col = "blue")
legend("topleft",
legend = c("既知の点", "補間された点", "補間線", "元の線"),
col = c("blue", "red", "red", "blue"),
pch = c(16, 4, NA, NA),
lty = c(NA, NA, 2, 1),
cex = 0.8)
# 2. `approxfun()` を使って補間関数を作成
# approx() と異なり、関数そのものを返す
f_approx <- approxfun(x_known, y_known)
# 補間関数を使って任意のX座標で値を計算できる
print(f_approx(2.5))
print(f_approx(6.8))
# 補間関数をプロット
curve(f_approx, from = min(x_known), to = max(x_known), add = TRUE, col = "darkgreen", lty = 3)
spline() 関数 (スプライン補間)
spline()
は、より滑らかな曲線を作成するスプライン補間(デフォルトでは3次スプライン)を実行します。
# 1. 基本的なスプライン補間
# 既知のデータ点 (より多くの点で滑らかさが出やすい)
x_known_spline <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
y_known_spline <- c(10, 15, 13, 20, 18, 25, 22, 30, 28, 35)
# より細かいX座標で補間
x_interpolate_fine <- seq(min(x_known_spline), max(x_known_spline), length.out = 100)
# spline() を使って補間
result_spline <- spline(x_known_spline, y_known_spline, xout = x_interpolate_fine)
print("--- spline() によるスプライン補間結果 (一部) ---")
print(head(result_spline)) # 結果が多いため先頭のみ表示
# 結果の可視化
plot(x_known_spline, y_known_spline,
main = "スプライン補間 (spline())",
xlab = "X", ylab = "Y",
pch = 16, col = "blue", cex = 1.5,
xlim = range(x_known_spline),
ylim = range(y_known_spline))
# スプラインで結ばれた線をプロット
lines(result_spline, col = "purple", lwd = 2)
legend("topleft",
legend = c("既知の点", "スプライン補間線"),
col = c("blue", "purple"),
pch = c(16, NA),
lty = c(NA, 1),
lwd = c(NA, 2),
cex = 0.8)
# 2. `splinefun()` を使って補間関数を作成
# spline() と異なり、関数そのものを返す
f_spline <- splinefun(x_known_spline, y_known_spline)
# 補間関数を使って任意のX座標で値を計算できる
print(f_spline(3.5))
print(f_spline(7.2))
# 補間関数をプロット (元のデータ点と一緒に)
plot(x_known_spline, y_known_spline,
main = "スプライン補間関数 (splinefun())",
xlab = "X", ylab = "Y",
pch = 16, col = "blue", cex = 1.5)
curve(f_spline, from = min(x_known_spline), to = max(x_known_spline), add = TRUE, col = "darkorange", lwd = 2)
zoo
パッケージは、時系列データを含む様々なデータ型における欠損値補間に非常に強力な機能を提供します。
# zoo パッケージをインストールしていない場合は、以下を実行
# install.packages("zoo")
library(zoo)
# 欠損値を含むデータ
data_with_na <- c(10, 12, NA, 16, NA, NA, 22, 24, NA, 30)
index_data <- 1:length(data_with_na)
# 元のデータをプロット
plot(index_data, data_with_na,
main = "欠損値補間 (zooパッケージ)",
xlab = "インデックス", ylab = "値",
pch = 16, col = "darkgray", cex = 1.5,
ylim = range(data_with_na, na.rm = TRUE) * c(0.9, 1.1)) # NAを除いて範囲を調整
points(index_data[is.na(data_with_na)], rep(min(data_with_na, na.rm = TRUE) - 2, sum(is.na(data_with_na))),
pch = 8, col = "red", cex = 1.2) # NAの位置をマーク
# 1. `na.approx()` による線形補間
interpolated_linear <- na.approx(data_with_na, na.rm = FALSE) # na.rm=FALSEで元の長さと型を維持
print("--- na.approx() による線形補間結果 ---")
print(interpolated_linear)
# 補間結果をプロット
lines(index_data, interpolated_linear, col = "green", lty = 2, lwd = 1.5)
points(index_data[is.na(data_with_na)], interpolated_linear[is.na(data_with_na)],
col = "darkgreen", pch = 17, cex = 1.2) # 補間されたNAの値を強調
# 2. `na.spline()` によるスプライン補間
interpolated_spline <- na.spline(data_with_na, na.rm = FALSE)
print("--- na.spline() によるスプライン補間結果 ---")
print(interpolated_spline)
# 補間結果をプロット
lines(index_data, interpolated_spline, col = "orange", lty = 3, lwd = 1.5)
points(index_data[is.na(data_with_na)], interpolated_spline[is.na(data_with_na)],
col = "darkorange", pch = 18, cex = 1.2) # 補間されたNAの値を強調
# 3. `na.locf()` によるLast Observation Carried Forward (前方補間)
# 直前の観測値で欠損値を埋める
interpolated_locf <- na.locf(data_with_na, na.rm = FALSE)
print("--- na.locf() による前方補間結果 ---")
print(interpolated_locf)
# 4. `na.nocb()` によるNext Observation Carried Backward (後方補間)
# 直後の観測値で欠損値を埋める
interpolated_nocb <- na.locf(data_with_na, fromLast = TRUE, na.rm = FALSE)
print("--- na.nocb() による後方補間結果 ---")
print(interpolated_nocb)
legend("bottomright",
legend = c("元の点", "NA (元の位置)", "線形補間線", "線形補間されたNA",
"スプライン補間線", "スプライン補間されたNA"),
col = c("darkgray", "red", "green", "darkgreen", "orange", "darkorange"),
pch = c(16, 8, NA, 17, NA, 18),
lty = c(NA, NA, 2, NA, 3, NA),
lwd = c(NA, NA, 1.5, NA, 1.5, NA),
cex = 0.8)
akima パッケージ (二次元補間)
akima
パッケージは、特に散布された不規則な二次元データからの補間に強みを持っています。等間隔のグリッドデータに変換したい場合などに非常に有用です。
- 使用例
# install.packages("akima") # インストールしていない場合 library(akima) # 不規則な二次元データ (x, y, z) set.seed(123) x <- runif(50, 0, 10) y <- runif(50, 0, 10) z <- sin(x/2) + cos(y/2) + rnorm(50, 0, 0.2) # zはx, yの関数 + ノイズ # 新しいグリッド点を生成 x_grid <- seq(0, 10, length.out = 30) y_grid <- seq(0, 10, length.out = 30) # interp() で補間 # method="linear" (デフォルト) または "akima" interp_result <- interp(x, y, z, xo = x_grid, yo = y_grid, method = "linear") # 結果の可視化 (image または contour) image(interp_result, main = "Akimaパッケージによる二次元補間 (interp)") contour(interp_result, add = TRUE) points(x, y, pch = 16, cex = 0.8) # 元のデータ点 # 特定の点での補間 (interpp) new_points_x <- c(2.5, 7.5) new_points_y <- c(2.5, 7.5) interpp_result <- interpp(x, y, z, xo = new_points_x, yo = new_points_y) print("--- interpp() による特定点での補間結果 ---") print(interpp_result)
- 特徴
- 散布データからの補間
規則的なグリッド状ではない (x_i,y_i,z_i) のデータから、指定した新しいグリッド上の z 値を推定します。 - 三角形分割と線形/高次補間
データ点を三角形分割し、その三角形内で線形補間を行ったり、より高次の補間(例: Akima Spline)もサポートします。
- 散布データからの補間
- 主な関数
interp()
,interpp()
pracma パッケージ (より多様な補間手法)
pracma
パッケージは、MATLABの関数に似た多くの数値計算機能を提供しており、補間に関してもいくつかの異なるアルゴリズムを実装しています。
- 使用例 (pchipの例)
# install.packages("pracma") # インストールしていない場合 library(pracma) x <- c(1, 2, 3, 4, 5) y <- c(1, 4, 1, 4, 1) # splineで波打ちやすいデータ # より細かいX座標 x_new <- seq(1, 5, length.out = 100) # spline補間 spline_res <- spline(x, y, xout = x_new) # pchip補間 (pracma::pchip) pchip_res <- pchip(x, y, x_new) plot(x, y, main = "Spline vs PCHIP Interpolation", xlab = "X", ylab = "Y", pch = 16, col = "blue", cex = 1.5) lines(spline_res, col = "red", lty = 2, lwd = 1.5, legend = "Spline") lines(x_new, pchip_res, col = "darkgreen", lty = 1, lwd = 1.5, legend = "PCHIP") legend("topright", legend = c("Original", "Spline", "PCHIP"), col = c("blue", "red", "darkgreen"), pch = c(16, NA, NA), lty = c(NA, 2, 1), lwd = c(NA, 1.5, 1.5)) # pchipは、splineがデータ点間で不自然な波を打つ場合に、より自然な補間曲線を提供する傾向があります。
- 特徴
interp1()
: 線形、スプライン、pchip
など、複数の補間方法を指定できます。特にpchip
は、spline
よりも"over-shoot"(不自然な波打ち)が少ないという特性があります。interp2()
: 2次元のグリッドデータからの補間。
- 主な関数
interp1()
(1次元補間),interp2()
(2次元補間),pchip()
(Piecewise Cubic Hermite Interpolation)
fields パッケージ (空間データ補間、サーフェスフィッティング)
fields
パッケージは、空間統計や空間データの可視化に特化しており、薄板スプライン(Thin Plate Spline)などの高度な補間手法を提供します。これは、地理空間データなど、不規則な位置にあるデータからの滑らかな表面の推定に特に適しています。
- 使用例 (Tps)
# install.packages("fields") # インストールしていない場合 library(fields) # 散布データ (x, y, z) set.seed(456) x_coords <- runif(50, 0, 10) y_coords <- runif(50, 0, 10) z_values <- exp(-( (x_coords-5)^2 + (y_coords-5)^2 ) / 5) + rnorm(50, 0, 0.05) # Tpsモデルの作成 tps_model <- Tps(cbind(x_coords, y_coords), z_values) # 新しいグリッド上での予測 # pred.grid() を使って予測用のグリッドを作成 pred_grid <- make.grid(list(x = seq(0, 10, length.out = 30), y = seq(0, 10, length.out = 30))) z_pred <- predict(tps_model, pred_grid) # 結果の可視化 par(mfrow = c(1, 2)) # プロットを2つ並べる plot(tps_model, main = "Tpsモデルの可視化") # モデルの診断プロット # 補間結果の表面プロット # プロットのために行列に変換 z_matrix <- matrix(z_pred, nrow = length(unique(pred_grid$x))) image.plot(x = unique(pred_grid$x), y = unique(pred_grid$y), z = z_matrix, main = "Thin Plate Spline 補間", xlab = "X", ylab = "Y") points(x_coords, y_coords, pch = 16, cex = 0.8) # 元のデータ点 par(mfrow = c(1, 1)) # プロットレイアウトを元に戻す
- 特徴
- 薄板スプライン
物理的な薄い板の曲がりを模倣し、データ点を通る滑らかな表面を推定します。 - クリギング
空間相関を考慮に入れた最適な線形不偏予測を行います。単なる補間以上の、統計的なモデリングに基づいた手法です。
- 薄板スプライン
- 主な関数
Tps()
(Thin Plate Spline),Krig()
(クリギング - 空間補間のための地統計学的手法)
mgcv パッケージ (一般化加法モデル - GAM)
mgcv
パッケージは、一般化加法モデル(GAM)を実装しており、平滑化スプライン(Smoothing Spline)をモデルの一部として使用できます。これは、補間よりもむしろデータにフィットする"滑らかなトレンド"を推定するのに使われますが、結果として補間のような効果が得られます。オーバーフィッティングを避けるために、平滑化パラメータを自動的に選択します。
- 使用例 (1次元の例)
# install.packages("mgcv") # インストールしていない場合 library(mgcv) # ノイズを含むデータ set.seed(789) x <- seq(0, 2*pi, length.out = 50) y <- sin(x) + rnorm(50, 0, 0.3) # GAMモデルを作成 (s() は平滑化スプラインを示す) gam_model <- gam(y ~ s(x)) # 予測 x_predict <- data.frame(x = seq(0, 2*pi, length.out = 200)) y_predict <- predict(gam_model, newdata = x_predict) plot(x, y, main = "GAMによる平滑化 (mgcv)", xlab = "X", ylab = "Y", pch = 16, col = "blue", cex = 1.2) lines(x_predict$x, y_predict, col = "purple", lwd = 2) legend("topright", legend = c("Original Data", "GAM Smooth"), col = c("blue", "purple"), pch = c(16, NA), lty = c(NA, 1), lwd = c(NA, 2))
- 特徴
- 平滑化スプライン
データ点を通るだけでなく、ある程度の"粗さ"(平滑化の度合い)を許容して、ノイズの影響を受けにくい滑らかな曲線や表面を推定します。 - 統計的モデリング
補間自体が目的というよりは、データに潜む関係性をモデル化する際に、柔軟な非線形関係を表現するためにスプラインを使用します。
- 平滑化スプライン
- 主な関数
gam()
ggplot2
は直接的な補間関数ではありませんが、データの可視化においてgeom_smooth()
関数を使用することで、線形回帰、LOESS(Locally Estimated Scatterplot Smoothing)、またはGAMなどを用いてデータにフィットする滑らかな曲線を描画できます。これは「補間」というよりも「平滑化」ですが、多くの場合、データ点間のトレンドを示すために利用されます。
- 使用例
# install.packages("ggplot2") # インストールしていない場合 library(ggplot2) data_df <- data.frame( x = c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), y = c(10, 15, 13, 20, 18, 25, 22, 30, 28, 35) + rnorm(10, 0, 1) ) ggplot(data_df, aes(x = x, y = y)) + geom_point(color = "blue", size = 3) + geom_smooth(method = "loess", se = TRUE, color = "red", linetype = "dashed", fill = "pink") + # LOESS平滑化 geom_smooth(method = "lm", se = FALSE, color = "green") + # 線形回帰 geom_smooth(method = "gam", formula = y ~ s(x), se = TRUE, color = "purple") + # GAM平滑化 labs(title = "ggplot2によるデータ平滑化", x = "X軸", y = "Y軸") + theme_minimal()
- 特徴
- データのトレンドを視覚的に捉えるのに最適。
- モデル(
method
引数で指定)に基づいて平滑化を行う。 - 標準誤差の帯も表示できる。
- 主な関数
geom_smooth()