Rの関数:filter {stats}

Rの関数から filter {stats} を確認します。

関数 filter とは

filter は、時系列データに対して「線形フィルタリング(Linear Filtering)」を行うための関数です。

時系列分析において、データを平滑化(スムージング)してトレンドを抽出したり、過去の値から現在の値を予測するモデル(ARモデルなど)を構築したりする際に、基礎的な計算エンジンとして機能します。

本関数は、引数の指定によって「畳み込み(Convolution / 移動平均)」「再帰的計算(Recursive / 自己回帰)」という、異なる2つの計算方式を切り替えて実行できる点が特徴です。

関数 filterの引数

args(filter)
function (x, filter, method = c("convolution", "recursive"), 
    sides = 2L, circular = FALSE, init = NULL) 
NULL
  1. x

    • フィルタリングの対象となる時系列データです。
    • 単変量または多変量の数値ベクトル、あるいは時系列(ts)オブジェクトを指定します。
  2. filter

    • 適用するフィルタの係数(重み)を表す数値ベクトルです。
    • method"convolution" の場合は移動平均の重みとして、method"recursive" の場合は自己回帰の係数として働きます。
  3. method

    • フィルタリングの計算方式を指定する文字列です。
    • "convolution" (デフォルト):

      • 畳み込み計算を行います。過去の入力データの加重平均をとる方式であり、MA(移動平均)モデルの計算に相当します。
    • "recursive":

      • 再帰的計算を行います。過去の出力結果を現在の計算に利用する方式であり、AR(自己回帰)モデルの計算に相当します。無限に影響が続くIIR(無限インパルス応答)フィルタとなります。
  4. sides

    • method = "convolution" の際に、フィルタをかける方向を指定する整数です。
    • 1: 片側フィルタ。過去のデータのみを参照します。リアルタイムのデータ処理や予測で用いられます。
    • 2 (デフォルト): 両側フィルタ。過去と将来のデータを対称に参照します。事後的なデータ解析における中心化移動平均として用いられます。
  5. circular

    • method = "convolution" の際に、データの端を巡回(循環)させるかどうかの論理値です。
    • デフォルト: FALSE
    • FALSE の場合、端のデータ不足部分は NA となります。TRUE の場合、データがループしていると見なして計算を行い、NA を発生させません。
  6. init

    • method = "recursive" の際に用いる、フィルタリング開始前の初期状態ベクトルです。
    • デフォルト: NULL(すべて0として計算されます)。

シミュレーションコード

以下に、stats::filter の機能を確認するためのシミュレーションコードを示します。

本シミュレーションは2つのパートに分かれています。

  1. method = "convolution" の検証:

    • ギザギザしたノイズを持つデータに対し、sides = 1(片側)と sides = 2(両側)の移動平均を適用し、結果の位相(タイミングのズレ)や端の処理の違いを確認します。
  2. method = "recursive" の検証:

    • 最初の1時点のみに値がある「インパルスデータ」に対し、再帰フィルタを適用し、影響がどのように波及・減衰していくかを確認します。

method = ‘convolution’ (移動平均) の確認

# パッケージの読み込み
# 注: dplyr パッケージの filter 関数との衝突を避けるため、
# 関数呼び出し時は stats::filter と明記します。
library(ggplot2)
library(tidyr)
library(gridExtra)

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


# 1-1. サンプルデータの生成 (サイン波 + ノイズ)
n_time <- 100
time_idx <- 1:n_time
true_wave <- sin(2 * pi * time_idx / 20)
obs_data <- true_wave + rnorm(n_time, mean = 0, sd = 0.4)

# 1-2. フィルタ係数の作成 (幅5の単純移動平均)
ma_coef <- rep(1 / 5, 5)

# 1-3. フィルタの適用
# sides = 1: 過去4時点と現在の合計5時点を平均
res_side1 <- stats::filter(obs_data, filter = ma_coef, method = "convolution", sides = 1)

# sides = 2: 過去2時点、現在、将来2時点の合計5時点を平均
res_side2 <- stats::filter(obs_data, filter = ma_coef, method = "convolution", sides = 2)

# データフレーム化
df_conv <- data.frame(
  Time = time_idx,
  Observed = obs_data,
  Side1 = as.numeric(res_side1),
  Side2 = as.numeric(res_side2)
)

df_conv_long <- pivot_longer(df_conv,
  cols = c("Side1", "Side2"),
  names_to = "FilterType", values_to = "FilteredValue"
)

# ラベルの整形
df_conv_long$FilterType <- factor(df_conv_long$FilterType,
  levels = c("Side1", "Side2"),
  labels = c("sides = 1 (片側/過去のみ)", "sides = 2 (両側/中心化)")
)

# 可視化
p1 <- ggplot(df_conv_long, aes(x = Time)) +
  geom_line(aes(y = Observed), color = "gray70", linewidth = 0.5) +
  geom_line(aes(y = FilteredValue, color = FilterType), linewidth = 1.2) +
  facet_wrap(~FilterType, ncol = 1) +
  scale_color_manual(values = c("blue", "darkgreen")) +
  labs(
    title = "畳み込みフィルタ (Convolution) による移動平均の比較",
    x = "時間 (Time)",
    y = "値 (Value)"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

print(p1)
Figure 1

Figure 1 のとおり、sides = 1(上図)の場合、常に過去のデータを見て平均をとるため、山の頂上が右側(将来方向)に少しズレて遅れます。

ただし、最新のデータに対してもNAにならず値を計算可能です。

sides = 2(下図)の場合、過去と将来を対称に見るため、山の頂上の位置が正しく維持されます。

しかし、過去または将来のデータが存在しない端の時点(最初と最後)は NA となります。

method = ‘recursive’ (自己回帰) の確認

# 2-1. サンプルデータの生成 (インパルス入力)
# 最初の時点だけ 10 を持ち、他はすべて 0 のインパルスデータ
impulse_data <- c(10, rep(0, 49))
time_impulse <- 1:50

# 2-2. フィルタ係数の作成
# AR(2)プロセスに相当する係数を設定
ar_coef <- c(0.8, -0.5)

# 2-3. 再帰フィルタの適用
res_recursive <- stats::filter(impulse_data, filter = ar_coef, method = "recursive")

df_rec <- data.frame(
  Time = time_impulse,
  Impulse = impulse_data,
  Response = as.numeric(res_recursive)
)

# 可視化
p2 <- ggplot(df_rec, aes(x = Time)) +
  geom_bar(aes(y = Impulse, fill = "入力 (インパルス)"), stat = "identity", width = 0.5, alpha = 0.3) +
  geom_line(aes(y = Response, color = "出力 (再帰応答)"), linewidth = 1.2) +
  geom_point(aes(y = Response, color = "出力 (再帰応答)"), size = 2) +
  geom_hline(yintercept = 0, color = "black", linewidth = 0.5) +
  scale_fill_manual(name = "", values = c("入力 (インパルス)" = "gray")) +
  scale_color_manual(name = "", values = c("出力 (再帰応答)" = "red")) +
  labs(
    title = "再帰フィルタ (Recursive) によるインパルス応答",
    x = "時間 (Time)",
    y = "値 (Value)"
  ) +
  theme_minimal() +
  theme(legend.position = "bottom")

print(p2)
Figure 2

Figure 2 のとおり、入力データ(灰色の棒)は最初の時点しか存在しません。

しかし、再帰フィルタを適用した出力(赤線)は、減衰しながら振動を続けています。

計算式 \(y_t = x_t + 0.8 \times y_{t-1} - 0.5 \times y_{t-2}\) が示す通り、過去の『出力結果』を次の計算にフィードバックさせるため、一度のインパルスが長期的な波紋となってシステムに影響を与え続ける様子が確認できます。

以上です。