まず試験 CSV の時間軸と単位を確認する
合成の demonstration data で units、missing values、finite numbers、duplicate または reversed timestamps を確認し、その後 check results と chart を保存します。
合成加速度データに3サンプルの後方移動平均を適用し、すべてのフィルタ値を手計算で確認し、フィルタによる1サンプルのアライメント遅延を明示します。元のCSVは変更せず、処理済みデータは新しいフォルダに書き出します。
この翻訳はAIで作成しました。コード、単位、数値は原文と併せて確認してください。各言語のネイティブ話者による校閲は、まだ完了していません。 English
対象読者このガイドは、サンプリングされた自動車または試験用センサーデータに対して簡単な初期平滑化手法を必要とし、フィルタによって生じる遅延も理解したい人向けです。
この例では、ウィンドウ幅が3サンプルの後方移動平均を使用します。サンプル n のフィルタ値は、現在のサンプルと直前の2サンプルの平均です: ma[n] = (x[n] + x[n-1] + x[n-2]) / 3。したがって、最初の2サンプルには完全な3サンプルウィンドウがありません。
移動平均はサンプル間の急激な変動を抑えますが、時間関係も変化させます。0.2、0.3、0.4秒の時刻を含む3サンプルウィンドウの中心は0.3秒ですが、因果的な後方実装では0.4秒のサンプルが到着した時点で結果が生成されます。サンプル間隔が0.1秒の場合、この中心時刻と出力時刻のアライメント差は0.1秒です。
以下のデータセットは合成データで、この資料のために作成したものです。sensor_data.csv として保存してください。サンプル間隔は0.1秒です。信号は0付近から始まり、その後9 m/s^2付近まで上昇し、両方のレベルの周辺に意図的な変動を加えています。
time_s,accel_m_s2
0.0,0
0.1,3
0.2,-3
0.3,0
0.4,9
0.5,12
0.6,6
0.7,9
サンプルは8個あります。0.4秒より前の生データ値は0、3、-3、0です。0.4秒以降の値は9、12、6、9です。このデータは意図的に単純化されており、特定の車両センサーやノイズ分布を表しているとは主張しません。
最初の完全なウィンドウは0.2秒で終了します: (0 + 3 - 3) / 3 = 0。次のウィンドウは (3 - 3 + 0) / 3 = 0 です。生信号が0.4秒で初めて9に達したとき、ウィンドウ内の3つの値は-3、0、9なので、フィルタ結果は9ではなく2になります。
| 出力時刻 | 3つのサンプル | MA3 | ウィンドウ中心 | アライメント遅延 |
|---|---|---|---|---|
| 0.2 s | 0, 3, -3 | 0.000 | 0.1 s | 0.1 s |
| 0.3 s | 3, -3, 0 | 0.000 | 0.2 s | 0.1 s |
| 0.4 s | -3, 0, 9 | 2.000 | 0.3 s | 0.1 s |
| 0.5 s | 0, 9, 12 | 7.000 | 0.4 s | 0.1 s |
| 0.6 s | 9, 12, 6 | 9.000 | 0.5 s | 0.1 s |
| 0.7 s | 12, 6, 9 | 9.000 | 0.6 s | 0.1 s |
変化部分のフィルタ系列は0から9へ直接跳ぶのではなく、0、2、7、9となり、上昇が複数の出力に広がっていることが分かります。これが平滑化の効果であり、フィルタ済み信号を他のチャンネルと比較する際に時間関係を考慮する必要がある理由も示しています。
以下のスクリプトを moving_average_filter.py として保存してください。タイムスタンプが一定の0.1秒間隔で増加していることを確認し、3サンプルの後方平均を計算して、新しいCSVを書き出します。既存の出力フォルダは再利用しません。
import csv
from pathlib import Path
SOURCE = Path("sensor_data.csv")
OUTPUT_DIR = Path("outputs") / "moving_average_result"
OUTPUT = OUTPUT_DIR / "filtered_sensor_data.csv"
WINDOW = 3
EXPECTED_DT = 0.1
TOLERANCE = 1e-9
def main() -> None:
if not SOURCE.is_file():
raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
if OUTPUT_DIR.exists():
raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")
if WINDOW < 1 or WINDOW % 2 == 0:
raise ValueError("WINDOW must be a positive odd integer.")
samples = []
with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
reader = csv.DictReader(stream)
required = {"time_s", "accel_m_s2"}
if reader.fieldnames is None or not required.issubset(reader.fieldnames):
raise ValueError("CSV is missing a required column.")
for row in reader:
samples.append((float(row["time_s"]), float(row["accel_m_s2"])))
if len(samples) < WINDOW:
raise ValueError("Not enough samples for the moving-average window.")
for index in range(1, len(samples)):
dt = samples[index][0] - samples[index - 1][0]
if abs(dt - EXPECTED_DT) > TOLERANCE:
raise ValueError(f"Unexpected sample interval at row {index + 2}: {dt}")
half_window = WINDOW // 2
output_rows = []
for index, (time_s, raw_value) in enumerate(samples):
if index < WINDOW - 1:
average_text = ""
center_text = ""
delay_text = ""
else:
window_values = [value for _, value in samples[index - WINDOW + 1:index + 1]]
average = sum(window_values) / WINDOW
center_time = samples[index - half_window][0]
alignment_delay = time_s - center_time
average_text = f"{average:.3f}"
center_text = f"{center_time:.1f}"
delay_text = f"{alignment_delay:.1f}"
output_rows.append({
"time_s": f"{time_s:.1f}",
"raw_accel_m_s2": f"{raw_value:g}",
"ma3_accel_m_s2": average_text,
"window_center_time_s": center_text,
"alignment_delay_s": delay_text,
})
OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
OUTPUT_DIR.mkdir()
fields = [
"time_s", "raw_accel_m_s2", "ma3_accel_m_s2",
"window_center_time_s", "alignment_delay_s"
]
with OUTPUT.open("x", encoding="utf-8", newline="") as stream:
writer = csv.DictWriter(stream, fieldnames=fields)
writer.writeheader()
writer.writerows(output_rows)
valid_outputs = len(samples) - WINDOW + 1
print(f"Input samples: {len(samples)}.")
print(f"MA{WINDOW} outputs with full windows: {valid_outputs}.")
print(f"Nominal alignment delay: {half_window * EXPECTED_DT:.1f} s.")
print(f"Output: {OUTPUT.as_posix()}")
if __name__ == "__main__":
main()
最初の2つのフィルタ済みセルは、完全な3サンプルウィンドウが利用できないため空欄になるはずです。残り6つのフィルタ値は0.000、0.000、2.000、7.000、9.000、9.000になるはずです。
time_s,raw_accel_m_s2,ma3_accel_m_s2,window_center_time_s,alignment_delay_s
0.0,0,,,
0.1,3,,,
0.2,-3,0.000,0.1,0.1
0.3,0,0.000,0.2,0.1
0.4,9,2.000,0.3,0.1
0.5,12,7.000,0.4,0.1
0.6,6,9.000,0.5,0.1
0.7,9,9.000,0.6,0.1
以下の期待されるコンソール出力は、合成データとコードから手作業で導出したものです。実際の実行ログではありません。
Input samples: 8.
MA3 outputs with full windows: 6.
Nominal alignment delay: 0.1 s.
Output: outputs/moving_average_result/filtered_sensor_data.csv| 問題 | 確認すること |
|---|---|
| 予期しないサンプル間隔 | 欠落サンプル、重複タイムスタンプ、または誤った EXPECTED_DT 値を確認します。 |
| フィルタ済み信号が遅れて見える | 後方ウィンドウは出力タイムスタンプより前を中心とするデータを要約していることを考慮します。 |
| 重要なピークが小さくなる | 移動平均は短時間のイベントを隣接サンプルに広げます。 |
| 平滑化が強すぎる | ウィンドウ長を短くし、ノイズ低減と時間特性のバランスを再評価します。 |
| 平滑化が弱すぎる | より長いウィンドウが有効な場合もありますが、時間方向のぼやけと公称遅延も増えます。 |
移動平均は単純なローパス平滑化手法であり、あらゆるセンサーに適用できる万能フィルタではありません。急激な変動を低減できますが、ピークを弱めたり、短時間イベントの開始点や終了点をぼかしたりすることもあります。これは振動、衝撃、故障検出、しきい値ベースの解析で重要です。
ここで報告する0.1秒の遅延は、0.1秒ごとにサンプリングされた3サンプルウィンドウについて、後方ウィンドウの中心時刻と結果が利用可能になるタイムスタンプとの差です。すべての信号特徴やすべてのフィルタ実装に共通する遅延として扱うべきではありません。
実際の自動車データには、不均一サンプリング、欠落サンプル、センサーバイアス、飽和、複数の同期チャンネル、またはサンプリング周波数と帯域要件から設計したフィルタを必要とする周波数成分が含まれる場合があります。工学的な結論を導く前に、必ず生信号を保存し、フィルタリング方法を記録してください。
2026-09-20 · 手作業で確認した例 · 対象: Python 3.12 · 標準ライブラリ: csv, pathlib · 実行なし
説明と例は独自に作成しました。関連する動作や概念は、以下の公式資料で確認できます。