Rの関数:trendfilter {genlasso}

Rの関数から trendfilter {genlasso} を確認します。

関数 trendfilter とは

trendfilter は、トレンドフィルタリング(Trend Filtering) と呼ばれるノンパラメトリック回帰手法を実行するための関数です。

この手法は、観測データに含まれるノイズを取り除き、背後にある滑らかなトレンドを抽出することを目的としています。

通常の平滑化スプライン(Smoothing Splines)と似ていますが、最大の違いは「L1正則化(Lasso)」を用いている点です。

具体的には、推定値の隣り合う値同士の差分(1階差分、2階差分など)の絶対値の和にペナルティを課すことで推定を行います。

L1ペナルティの性質により、多くの差分が厳密にゼロになります。

  • 0次(ord=0):

    • Fused Lassoと等価で、階段状(区分定数)のトレンドを推定します。
  • 1次(ord=1):

    • 折れ線グラフ状(区分線形)のトレンドを推定します。
  • 2次以上:

    • より滑らかな曲線(区分多項式)を推定します。

関数 trendfilter の 引数

library(genlasso)
args(trendfilter)
function (y, pos, X, ord = 1, approx = FALSE, maxsteps = 2000, 
    minlam = 0, rtol = 1e-07, btol = 1e-07, eps = 1e-04, verbose = FALSE) 
NULL
  1. y

    • 被説明変数となる数値ベクトルです。
    • ノイズを含んだ観測データそのものを渡します。
  2. pos

    • データの観測位置(時間や場所など)を示す数値ベクトルです。
    • デフォルト: NULL(等間隔のデータ \(1, 2, \dots, n\) と仮定されます)。
    • データが不等間隔で取得されている場合(例:欠損がある、測定タイミングがバラバラ)に、その位置情報を正しく反映させるために使用します。
  3. X

    • 計画行列(Design Matrix)です。
    • デフォルト: NULL(信号ノイズ除去問題として扱われます)。
    • 通常のトレンドフィルタリング(信号ノイズ除去)ではなく、回帰モデル \(y = X\beta + \epsilon\) において係数ベクトルの並びにトレンド制約(隣り合う係数が近い値をとる等)を課したい場合に使用します。
  4. ord

    • トレンド推定に用いる多項式の次数(整数)です。
    • デフォルト: 1
    • 詳細:

      • 0: 区分定数(階段状)
      • 1: 区分線形(折れ線)
      • 2: 区分二次曲線
    • 制約: 負でない整数である必要があります。
  5. approx

    • 近似アルゴリズムを使用するかどうかの論理値です。
    • デフォルト: FALSE
    • データサイズが非常に大きい場合、厳密解を求めるパスアルゴリズムは時間がかかるため、TRUE にして計算を高速化させることができます。
  6. maxsteps

    • アルゴリズムが解のパス(Solution Path)を計算する際の最大ステップ数です。
    • デフォルト: 2000
    • 計算が収束しない、あるいはパスが長すぎる場合に計算を打ち切るための制限です。
  7. minlam

    • 計算する正則化パラメータ \(\lambda\) の最小値です。
    • デフォルト: 0
    • パスをどこまで計算するか(どこまでモデルを複雑にするか)の下限を設定します。
  8. rtol

    • 相対許容誤差(Relative Tolerance)です。
    • デフォルト: 1e-07
  9. btol

    • Boundary Tolerance です。
    • デフォルト: 1e-07
    • 変数が境界(制約条件)にあるかどうかを判定する際の閾値です。
  10. eps

    • リッジペナルティ項の係数です。
    • デフォルト: 1e-04
    • 引数 X が指定され、かつ列数が行数より多い場合(\(p > n\))の数値的不安定性を防ぐために、わずかなリッジペナルティを追加します。
  11. verbose

    • 進行状況を表示するかどうかの論理値です。
    • デフォルト: FALSE

シミュレーションコード

以下に、trendfilter の挙動を確認するためのサンプルデータを用いたシミュレーションコードを示します。

このシミュレーションでは、以下の特徴を持つ時系列データを生成します。

  1. 平坦な区間(変化なし)
  2. 直線的な上昇(1次トレンド)
  3. 放物線のような曲線(2次トレンド)
  4. 急激な変化点

このデータに対し、次数(ord)を変えて trendfilter を適用し、それぞれの次数がどのようなトレンドを抽出する特性があるかを視覚的に比較します。

シミュレーションデータの生成

平坦 -> 線形上昇 -> 曲線 と変化するトレンドにノイズを付加した構造とします。

# パッケージの読み込み
library(ggplot2)
library(dplyr)
library(tidyr)

# 乱数シードの固定
seed <- 20260109
set.seed(seed)

# データ数
n <- 100
x <- 1:n

# 真のトレンド関数の作成(区分的に定義)
# 1-30: 平坦 (値: 10)
# 31-60: 線形上昇
# 61-100: 二次関数的な曲線
y_true <- numeric(n)
y_true[1:30] <- 10
y_true[31:60] <- seq(10, 20, length.out = 30)
# 61-100は滑らかにつなげるための二次関数
y_true[61:100] <- 20 - 0.05 * (1:40)^2 + 1.5 * (1:40)

# 観測データ y_obs (真のトレンド + ノイズ)
# ノイズは正規分布に従う
y_obs <- y_true + rnorm(n, mean = 0, sd = 1.5)

# データフレーム化
df_sim <- data.frame(
  x = x,
  y_obs = y_obs,
  y_true = y_true
)

# データの可視化
p_raw <- ggplot(df_sim, aes(x = x, y = y_obs)) +
  geom_point(color = "grey50", alpha = 0.7) +
  geom_line(aes(y = y_true), color = "black", linewidth = 1, linetype = "dashed") +
  labs(
    title = "シミュレーションデータ(観測値と真のトレンド)",
    subtitle = "黒破線が真のトレンド、灰色点が観測値",
    x = "時点 (Index)",
    y = "値 (Value)"
  ) +
  theme_minimal()

print(p_raw)
Figure 1

trendfilter による推定の実行

# 異なる次数(ord)でモデルを適合
# λ(正則化パラメータ)はクロスバリデーションで最適値を選択します

# 1. 0次トレンドフィルタリング (Fused Lasso: 階段状)
fit_ord0 <- trendfilter(y = y_obs, ord = 0)
cv_ord0 <- cv.trendfilter(fit_ord0) # 最適なλを選択
# 最適なλでの予測値を抽出
pred_ord0 <- predict(fit_ord0, lambda = cv_ord0$lambda.min)$fit

# 2. 1次トレンドフィルタリング (Linear Trend Filtering: 折れ線)
fit_ord1 <- trendfilter(y = y_obs, ord = 1)
cv_ord1 <- cv.trendfilter(fit_ord1)
pred_ord1 <- predict(fit_ord1, lambda = cv_ord1$lambda.min)$fit

# 3. 2次トレンドフィルタリング (Quadratic Trend Filtering: 曲線)
fit_ord2 <- trendfilter(y = y_obs, ord = 2)
cv_ord2 <- cv.trendfilter(fit_ord2)
pred_ord2 <- predict(fit_ord2, lambda = cv_ord2$lambda.min)$fit

# 結果を結合
df_results <- df_sim %>%
  mutate(
    Ord0_Step = as.numeric(pred_ord0),
    Ord1_Linear = as.numeric(pred_ord1),
    Ord2_Quad = as.numeric(pred_ord2)
  ) %>%
  pivot_longer(
    cols = c("Ord0_Step", "Ord1_Linear", "Ord2_Quad"),
    names_to = "Model",
    values_to = "Fitted"
  )

# モデル名のラベルを見やすく変更
df_results$Model <- factor(df_results$Model,
  levels = c("Ord0_Step", "Ord1_Linear", "Ord2_Quad"),
  labels = c("次数0 (階段状)", "次数1 (折れ線)", "次数2 (曲線)")
)

# 結果の可視化
p_res <- ggplot(df_results, aes(x = x)) +
  # 背景に観測データ
  geom_point(aes(y = y_obs), color = "grey70", alpha = 0.5) +
  # 推定されたトレンド
  geom_line(aes(y = Fitted, color = Model), linewidth = 1) +
  # 真のトレンド(比較用)
  geom_line(aes(y = y_true), color = "black", linetype = "dashed", linewidth = 1) +
  scale_color_brewer(palette = "Set1") +
  facet_wrap(~Model, ncol = 1) +
  labs(
    title = "各次数(ord)によるトレンド推定結果の比較",
    x = "時点 (Index)",
    y = "推定値 (Fitted Value)",
    color = "モデル"
  ) +
  theme_minimal()

print(p_res)
Figure 2

Figure 2 は、trendfilter 関数の次数(ord)の違いがトレンド推定の結果にどのような影響を与えるかを比較したグラフです。

上から順に、次数0、次数1、次数2の設定で推定されたトレンド(色付きの実線)が、真のトレンド(黒い破線)および観測データ(灰色の点)と重ねて描画されています。

上段:次数0(階段状 / 区分定数)
  • 推定された赤い実線が、細かい水平な線分の連続(ステップ関数)として描かれています。
  • 次数0 のトレンドフィルタリングは「値の変化点」をスパースにしようとします(「値の変化点」を少なくしようとします)。それゆえ、推定結果は階段状になります。
  • 前半の平坦な区間(Index 1-30)では真のトレンドをよく捉えています。しかし、中盤以降の「斜めに上昇する区間」や「曲線的に変化する区間」では、斜めの直線を表現できないため、細かい階段を積み重ねることで近似しようとしています。
中段:次数1(折れ線 / 区分線形)
  • 推定された青い実線が、直線を繋ぎ合わせた形(折れ線)として描かれています。
  • 次数1 のトレンドフィルタリングは「傾きの変化点」をスパースにしようとします。
  • このデータセットには「平坦 \(\rightarrow\) 直線的上昇 \(\rightarrow\) 曲線」という構造が含まれていますが、このモデルは中盤の直線的な上昇(Index 31-60)を他の2つの次数よりも適切に捉えています。
下段:次数2(曲線 / 区分二次)
  • 推定された緑の実線が、滑らかな曲線として描かれています。
  • 次数2 のトレンドフィルタリングは「曲率の変化点」を制御するため、結果は滑らかな二次スプライン曲線となります。
  • 特に後半(Index 60-100)の山なりになっている部分において、次数1(折れ線)よりもカクカクせず、真のトレンド(黒い破線)のカーブを追随しています。

Figure 2 は、trendfilter を使用する際、「分析対象のデータがどのような変化(階段状、折れ線状、曲線状)をしていると想定されるか」に合わせて、引数 ord を適切に選択することの重要性を示しています。

  • 急激なジャンプを見つけたい場合 \(\rightarrow\) 次数0
  • トレンドの転換点を見つけたい場合 \(\rightarrow\) 次数1
  • 滑らかな推移を抽出したい場合 \(\rightarrow\) 次数2

以上です。