数値的に安定かつ高速な移動平均と移動分散の実装
msum・mvar として公開しています。
- 移動和:系列を窓幅 \(w\) と同じ長さのチャンクに区切ると、どの窓も「前のチャンクの接尾辞+次のチャンクの接頭辞」という最大 2 つの部分に分解できます。すべてのチャンクの接頭辞と接尾辞の和は、累積和を使って一括で計算できます。これらを組み合わせれば、移動和と移動平均を求められます(§1)。
- 移動分散への拡張(部分ごとの統計量):各部分について保持する量を、「和」から「要素数・平均・偏差二乗和の 3 つ組」に置き換えます。このとき統計量は、その部分自身に含まれる要素(チャンクの先頭または末尾の値)を基準にした偏差から計算します。分散は値全体を平行移動しても変わらないため、計算結果を変えずに、誤差を窓内の局所的な値のスケールに抑えられます(§2.3)。
- 移動分散への拡張(統計量の結合):2 つの部分の分散統計量を Chan らのマージ式で結合し、1 つの窓の統計量を求めます(§2.4)。
対象とする問題
rolling(w) と同じ規約です)。
- 計算量が \(O(T)\):計算時間が窓幅 \(w\) に依存しないこと。日次データでは、\(w = 250\) のように長い窓も一般的に使われます。
- ベクトル化できる:全体がテンソル演算(累積和と要素ごとの演算)で構成され、時点方向に明示的な逐次ループを持たないこと。これにより、GPU や SIMD で効率よく実行できます。
- 数値的に安定している:誤差が窓内の局所的な変動のスケールで決まり、系列全体の水準・ドリフト・長さに依存しないこと。
- NaN・±inf が正確に伝播する:欠損値や無限大が、それを含む窓だけに影響し、含まない窓の結果には影響しないこと。
1. 移動和・移動平均:チャンク累積和
1.1. 大域的な累積和の差分は系列長とともに精度が劣化する
1.2. チャンク分割:どの窓も 2 つ以下の部分に分けられる
1.3. 部分和から移動和・移動平均を求める
msum・ma はこの方法で実装しています。
2. 移動分散への拡張
2.1. 二乗和の公式はデータの水準が大きいと桁落ちする
2.2. 安定な代替手法では速度かベクトル化が犠牲になる
unfold で各窓のビューを作り、var を適用する形で実装できます。筆者の環境で測定したところ、\(O(T)\) の方法より 8〜300 倍遅くなりました(§4)。計算時間が窓幅に比例するため、\(w\) が数百になる用途には適しません。
rolling はこの方法を使っています。ただし、時点ごとに前の状態を引き継ぐため、時点方向には並列化できません。本記事では数千系列のバッチを float32 で処理することを想定しているため、時点方向にも並列化できるテンソル演算を採用します。
2.3. 部分ごとの統計量と局所中心化
- 誤差を窓内のスケールに抑えられる:どの接頭辞も先頭要素を含み、どの接尾辞も末尾要素を含むため、基準値は必ずその部分自身の要素です(図 2)。各部分は窓の部分集合なので、偏差 \(y\) の大きさは窓内の値域を超えません。系列の水準が高い場合やドリフトがある場合でも、計算に現れる値は窓の近傍における差分だけです(図 3・§3.1)。
- NaN・±inf の影響を正確に限定できる:基準値が NaN や無限大の場合に影響を受けるのは、その要素自身を含む部分、つまり本来 NaN になるべき窓だけです(§3.2)。基準値にチャンクの平均のような集約値を使うと、チャンク内の 1 点にある NaN が、それを含まない窓にまで影響します。
2.4. Chan らのマージ式で 2 つの部分を結合する
2.5. アルゴリズム全体
- 系列の先頭に NaN を追加して長さを \(w\) の倍数に揃え、長さ \(w\) のチャンクに区切ります(§1.2)。
- 各チャンクで、先頭の値を基準にした偏差 \(y\) について \(y\) と \(y^2\) の累積和を計算し、すべての接頭辞の \((n, \bar m, M)\) を求めます。
- 同様に、末尾の値を基準にした偏差について逆向きの累積和を計算し、すべての接尾辞の \((n, \bar m, M)\) を求めます。
- 各窓について、マージ式から \(M_{A \cup B}\) を求め、\(w - \mathrm{ddof}\) で割ります。窓と各部分の対応は添字をずらすだけで得られるため、この段階も要素ごとの演算で処理できます。
- 先頭に追加した NaN により、先頭の \(w-1\) 時点が自動的に NaN になります。\(T < w\) の場合は、すべての時点が NaN になります。
mvar)。
cumsum で広く使われています。
2.6. 小さな数値例
| 部分 | 基準値 | 偏差 \(y\) | \(n\) | \(\bar m\) | \(M\) | \(\mu\) |
|---|---|---|---|---|---|---|
| \(A = (48)\) | 末尾の値 48 | \((0)\) | 1 | 0 | 0 | 48 |
| \(B = (49, 51)\) | 先頭の値 49 | \((0, 2)\) | 2 | 1 | \(4 - 2^2/2 = 2\) | 50 |
3. 数値安定性の性質
3.1. 誤差が窓の局所スケールに収まる理由
3.2. NaN・±inf の影響を窓内に限定できる理由
3.3. 実測
| ケース(float32) | 二乗和の公式 | 本手法 |
|---|---|---|
| オフセット:相対誤差(中央値) | 約 \(9 \times 10^{4}\)(有効桁が残らない) | \(6 \times 10^{-3}\)(入力の量子化誤差が下限) |
| オフセット:負の分散の割合 | 31% | 0 |
| ドリフト:相対誤差(中央値) | 約 \(72\) | \(1.3 \times 10^{-4}\) |
4. 速度
unfold + var)は計算量が \(O(Tw)\) であり、窓幅に比例して実行時間が増えます。筆者の環境(Apple シリコンの CPU、PyTorch 2.7、float32)で測定した結果は次の通りです。
| 形状 | 窓幅 | 二乗和の公式 | 窓ごと再計算 | 本手法 |
|---|---|---|---|---|
| (10000,) | 20 | 0.21 ms | 1.5 ms | 0.18 ms |
| (100, 10000) | 20 | 2.0 ms | 145 ms | 2.7 ms |
| (100, 10000) | 500 | 2.1 ms | 763 ms | 3.1 ms |
| (1000, 10000) | 500 | 21 ms | 7237 ms | 23 ms |
5. 関連手法との比較
sum などで採用されている標準的な手法です。
| 方式 | 計算量 | ベクトル化 | 数値安定性 |
|---|---|---|---|
| 二乗和の公式(移動和 2 本) | \(O(T)\) | 可 | 不安定(誤差因子 \(\mu^2/\sigma^2\)) |
| 大域累積和の差分 | \(O(T)\) | 可 | 系列長に比例して劣化(§1.1) |
| 窓ごとの再計算 | \(O(Tw)\) | 可 | 安定(\(O(\varepsilon w)\)) |
| 逐次 add/remove(pandas など) | \(O(T)\) | 時点方向は不可 | 概ね安定(除去の実装に依存) |
| 本手法(チャンク+局所中心化+マージ) | \(O(T)\) | 可 | 安定(最悪 \(O(\varepsilon w^2)\)、水準・系列長に非依存) |
6. 実装メモ
- 先頭に追加する NaN の役割:チャンク境界を揃えるために系列の先頭へ追加する値を NaN にすると、「先頭の \(w-1\) 時点は NaN」「\(T < w\) なら全時点が NaN」という出力規約を、専用の分岐なしで満たせます。NaN を \(w\) 個以上追加することで、実データの先頭に対しても直前のチャンクを用意できます。
- ddof の扱い:\(0 \le \mathrm{ddof} < w\) を前提とします。通常は標本分散の \(1\) か母分散の \(0\) です。
- 移動平均・移動標準偏差:移動和と移動平均は §1 のチャンク和だけで求められます。移動標準偏差は、移動分散の平方根として求めます。
- 負値のクランプ:本手法の \(M\) は、丸め誤差によってごくわずかに負になる可能性があります。一方、通常の 2 パス法では非負であることが保証されます。平方根を取る場合など、厳密な非負性が必要であれば、最後に 0 を下限としてクランプします。実測の条件(§3.3)では、負の値は観測されませんでした。
7. 設計上の限界
- 誤差の上界が 2 パス法より大きい:最悪の場合の誤差は \(O(\varepsilon w^2)\) であり、窓ごとの 2 パス法における \(O(\varepsilon w)\) より \(w\) 倍大きい上界です。速度よりも末尾の桁までの精度を優先する場合や、\(M\) が必ず非負である必要がある場合は、2 パス法が適しています(§3.1・§6)。
- ストリーミング処理には適さない:本手法はチャンク単位の一括計算を前提としています。1 点ずつ到着するデータを低遅延で逐次処理する場合は、Welford 法や add/remove 方式(§2.2)の方が適しています。本手法はバッチ計算向けです。
- NaN を無視して計算できない:窓に NaN が含まれていれば、その窓の結果も NaN になります。pandas の
min_periodsのように、NaN を除いた要素だけで計算する必要がある場合は、有効な要素数のカウントを含む別の構成が必要です。 - 入力は浮動小数点数に限られる:先頭への NaN の追加と偏差の計算を行うため、整数系列はあらかじめ浮動小数点数へ変換する必要があります。
8. まとめ
- 大域累積和の差分による移動和は、系列が長くなるほど精度が低下します。系列を窓幅と同じ長さのチャンクに区切ると、どの窓も「接尾辞+接頭辞」という最大 2 つの部分に分けられます。各部分の和は最大 \(w\) 項なので、移動和と移動平均を安定して求められます。
- 移動分散を二乗和の公式で求めると、誤差が \(\mu^2/\sigma^2\) に比例して増幅されます。一方、安定した窓ごとの再計算には \(O(Tw)\) の計算量が必要であり、逐次更新は時点方向に並列化できません。本手法では、各部分について保持する量を、和から「要素数・平均・偏差二乗和」に置き換えます。統計量をその部分自身の要素を基準にした偏差で表すことで、計算全体を局所的な差分だけで構成し、Chan らのマージ式で 2 つの部分を結合します。
- これにより、窓幅に依存しない計算量 \(O(T)\)、テンソル演算だけによる実装、水準・ドリフト・系列長に依存しない局所的な誤差、NaN と無限大の正確な伝播を同時に実現できます。実測した処理時間は、二乗和の公式の 0.9〜1.5 倍でした。
- 用いる要素技術は、1960〜80 年代に発表された Welford と Chan–Golub–LeVeque の古典的な手法です。本記事では、これらを移動窓のテンソル計算に組み合わせる構成を整理しました。実装は qfeval-functions の
msum・mvarで公開しています。
9. 参考文献
- [1] B. P. Welford, "Note on a Method for Calculating Corrected Sums of Squares and Products," Technometrics 4(3), 419―420, 1962. (Welford のオンライン更新式。逐次追加に対する平均と偏差二乗和の安定な更新)
- [2] Tony F. Chan, Gene H. Golub, Randall J. LeVeque, "Updating Formulae and a Pairwise Algorithm for Computing Sample Variances," COMPSTAT 1982, 30―41, 1982. (Chan–Golub–LeVeque による更新式とペアワイズアルゴリズム。本記事のマージ式の原典)
- [3] Tony F. Chan, Gene H. Golub, Randall J. LeVeque, "Algorithms for Computing the Sample Variance: Analysis and Recommendations," The American Statistician 37(3), 242―247, 1983. (同著者らによる分散計算アルゴリズムの誤差分析と推奨。シフト(中心化)の効果を含む)
- [4] Nicholas J. Higham, "Accuracy and Stability of Numerical Algorithms, Second Edition," Society for Industrial and Applied Mathematics, 2002. (丸め誤差解析の標準的教科書。総和・分散計算の誤差上界)
最終更新日: 2026年7月16日