Skip to content

データ品質の確認

このガイドでは、kazunokoで読み込んだイベント(検出データ)の品質を確認・検証する方法を説明します。


データ品質とは

読み込んだイベントが「良い品質」とは、以下の条件を満たしていることです。

✓ 期待したイベント数が取得できている
✓ スレッショルド値が適切に設定されている
✓ データに異常がない
✓ 検出パターンが物理的に妥当

イベント数の確認

実際に取得したイベント数

# イベント 100 個を読み込んで数える
uv run kazunoko read 100 | wc -l

# 結果例: 95
# → 95 個のイベントが取得できた(5 個はスキップ)

成功率の確認

# 成功率を計算
# 要求イベント数: 100
# 取得できた数: 95
# 成功率: 95 / 100 = 95%

# スクリプトで自動計算
python3 << 'EOF'
requested = 100
actual = 95
success_rate = (actual / requested) * 100
print(f"Success rate: {success_rate:.1f}%")
print(f"Skipped: {requested - actual} events")
EOF

期待値との比較

成功率 評価 対応
95-100% 優秀 特に対応不要
80-95% 良好 改善推奨
50-80% 要注意 早急に改善が必要
<50% 不良 スレッショルド値等を見直し

複数回実行して平均を取る

# 5 回実行して平均成功率を計算
total=0
for i in {1..5}; do
  count=$(uv run kazunoko read 100 | wc -l)
  echo "Run $i: $count events"
  total=$((total + count))
done

average=$((total / 5))
echo "Average: $average events"
echo "Success rate: $((average))%"

スレッショルド値の検証

現在のスレッショルド値を確認

uv run kazunoko status | grep threshold

出力例:

threshold_ch1   │ 280
threshold_ch2   │ 300
threshold_ch3   │ 290

スレッショルド値が適切か判断

現象 原因 対応
イベントがほぼ取得できない スレッショルド値が高すぎる 値を下げる(例: 300 → 250)
ノイズが多い スレッショルド値が低すぎる 値を上げる(例: 200 → 250)
イベント数が期待値と異なる スレッショルド値の調整が必要 スキャン実験で最適値を探す

スレッショルドスキャン実験

異なるスレッショルド値でイベント数を調べます。

# スレッショルド値をを変えながらイベント数をカウント
for threshold in 200 250 280 300 320 350; do
  count=$(uv run kazunoko measure "1:$threshold;2:$threshold;3:$threshold" 100 | wc -l)
  echo "Threshold: $threshold → Events: $count"
done

出力例:

Threshold: 200 → Events: 98  (低い → イベント多い)
Threshold: 250 → Events: 95
Threshold: 280 → Events: 87
Threshold: 300 → Events: 72  (標準)
Threshold: 320 → Events: 45
Threshold: 350 → Events: 12  (高い → イベント少ない)

グラフで可視化:

イベント数
  100 |     ●
   80 |        ●
   60 |           ●
   40 |              ●
   20 |                 ●
    0 |________________________
      200 250 280 300 320 350  (スレッショルド値)

最適なスレッショルド値の選択

目標イベント数を確定 → スレッショルドスキャンで得られたイベント数と比較
→ 最適なスレッショルド値を選択

例:

測定目標: 1000 イベント × 5 回 = 計 5000 イベント
1 回あたり必要: 1000 イベント
スレッショルド値 280 で約 870 イベント/100 回要求

計算: 1000 / 870 ≈ 1.15
→ 要求イベント数を 1150 に設定

データの統計的な確認

イベント数の分布

読み込んだイベントが正常に分布しているか確認します。

# イベント 1000 個を読み込んで詳細を確認
uv run kazunoko measure "1:300;2:320;3:310" 1000 > events.jsonl

# イベント数をカウント
wc -l events.jsonl

# 各チャネルのイベント数をカウント
cat events.jsonl | jq '.ch' | sort | uniq -c

出力例:

  1 ch1: 340 events
  1 ch2: 350 events
  1 ch3: 310 events

期待値との比較

各チャネルのイベント数がほぼ同数であるか確認します。

パターン 意味 対応
均等 (315±35) 正常 特に対応不要
やや偏り (280-380) 許容範囲 様子を見る
大きく偏り (<250 or >400) 異常 スレッショルド値を見直し

タイムスタンプの確認

イベントの時刻が正常に記録されているか確認します。

# 最初と最後のタイムスタンプを確認
head -1 events.jsonl | jq '.received_us'
tail -1 events.jsonl | jq '.received_us'

# 時間差を計算(マイクロ秒 → 秒)
# (最後のタイムスタンプ - 最初のタイムスタンプ) / 1,000,000

例:

最初: 1734321600000000 µs
最後: 1734321640000000 µs
差分: 40000000 µs = 40 秒

→ 40 秒間に 1000 イベント = 25 イベント/秒

イベント属性の確認

JSON フィールドの確認

# イベントの構造を確認
head -1 events.jsonl | jq 'keys'

出力例:

[
  "type",
  "status",
  "received_us",
  "event_count",
  "ch",
  "sensor_1",
  "sensor_2",
  ...
]

必須フィールドの確認

# event_count フィールドが全て存在するか確認
cat events.jsonl | jq 'select(.event_count == null)' | wc -l

# 結果が 0 なら全て OK

フィールド値の範囲確認

# event_count の最小値・最大値を確認
cat events.jsonl | jq '[.event_count] | min_by(.) | .[0]'
cat events.jsonl | jq '[.event_count] | max_by(.) | .[0]'

# 結果が 1 ~ 1000 の範囲内か確認

データの異常検出

重複イベントの確認

同一の event_count を持つイベントが複数ないか確認します。

# event_count の重複をチェック
cat events.jsonl | jq '.event_count' | sort | uniq -d | head -10
  • 出力が空 → 重複なし(正常)
  • 出力に数字がある → 重複あり(異常)

欠落イベントの確認

event_count が 1, 2, 3, ... と連続しているか確認します。

# event_count をソートして確認
cat events.jsonl | jq '.event_count' | sort -n | \
  awk 'NR==1{last=$1; next} {if($1-last!=1){print "Gap at", last, "->", $1} last=$1}'
  • 出力が空 → 欠落なし(正常)
  • 出力にメッセージがある → 欠落あり(異常)

タイムスタンプの異常

タイムスタンプが単調増加しているか確認します。

# タイムスタンプが前後逆転していないか確認
cat events.jsonl | jq '.received_us' | \
  awk 'NR==1{last=$1; next} {if($1<last){print "Time went backward at", NR} last=$1}'
  • 出力が空 → 正常
  • 出力にメッセージがある → 異常(シリアル通信エラーの可能性)

データの物理的妥当性

イベント発生パターン

ミューオンの検出パターンが物理的に妥当か確認します。

# チャネル別のイベント数を確認
cat events.jsonl | jq '.ch' | sort | uniq -c

# 各チャネルの検出確率が 1/3 ~ 1/2 程度か確認
# (ミューオンが斜めに入射するため、複数チャネルで検出)

正常なパターン:

Ch1: 30-40%
Ch2: 30-40%
Ch3: 30-40%
複合: 一部のイベントで複数チャネルが反応

センサー値の確認

各センサーの値が妥当な範囲か確認します。

# センサー値の統計を確認
cat events.jsonl | jq '.sensor_1' | \
  awk '{sum+=$1; sq+=($1*$1); n++} END {
    mean=sum/n;
    variance=sq/n - mean*mean;
    stddev=sqrt(variance);
    print "Mean:", mean, "StdDev:", stddev
  }'

期待値(例):

Sensor 値: 0 ~ 4095 (12-bit ADC)
期待平均: 2000 ± 500
標準偏差: 500 ~ 1000

CSV 形式での確認

CSV に変換

# JSON から CSV に変換
uv run kazunoko read 100 --format csv > events.csv

# または、コマンドラインで変換
cat events.jsonl | jq -r '[.event_count, .ch, .received_us, .sensor_1] | @csv' > events.csv

スプレッドシートで開く

# CSV をスプレッドシートアプリケーションで開く(macOS)
open events.csv

# または、コマンドラインビューア
cat events.csv | head -20

データの可視化

# column コマンドで整形表示
cat events.csv | column -t -s, | head -20

記録されたデータの検証

ファイルサイズの確認

# ファイルサイズを確認
ls -lh events.jsonl

# イベント 1000 個で約 50-100 KB が目安

ファイル破損の確認

# JSON が有効か確認
jq empty events.jsonl && echo "Valid JSON" || echo "Invalid JSON"

# または、イベント数を確認
jq -s 'length' events.jsonl

複数ファイルの検証

# 複数ファイルの合計イベント数を確認
total=0
for file in *.jsonl; do
  count=$(jq -s 'length' "$file")
  echo "$file: $count events"
  total=$((total + count))
done

echo "Total: $total events"

Python スクリプトでの検証

データ品質チェック関数

import json
from pathlib import Path

def check_data_quality(jsonl_file):
    """データ品質をチェック"""
    events = []
    with open(jsonl_file) as f:
        for line in f:
            events.append(json.loads(line))

    print(f"Total events: {len(events)}")

    # イベント数の確認
    if len(events) == 0:
        print("⚠ No events found")
        return

    # フィールド確認
    required_fields = ["event_count", "ch", "received_us"]
    for field in required_fields:
        missing = sum(1 for e in events if field not in e)
        if missing > 0:
            print(f"⚠ {missing} events missing '{field}' field")

    # event_count の確認
    event_counts = sorted([e["event_count"] for e in events])
    expected = list(range(1, len(events) + 1))
    if event_counts != expected:
        print("⚠ event_count is not sequential")
        gaps = [i for i in range(len(expected)) if event_counts[i] != expected[i]]
        print(f"  Gaps at: {gaps[:5]}...")

    # チャネル別の分布
    from collections import Counter
    channels = [e["ch"] for e in events]
    ch_dist = Counter(channels)
    print("Channel distribution:")
    for ch, count in sorted(ch_dist.items()):
        pct = (count / len(events)) * 100
        print(f"  Ch{ch}: {count} events ({pct:.1f}%)")

    # タイムスタンプの確認
    timestamps = [e["received_us"] for e in events]
    if timestamps != sorted(timestamps):
        print("⚠ Timestamps are not monotonically increasing")

    print("✓ Data quality check completed")

# 使用
check_data_quality("events.jsonl")

統計分析

import json
import statistics

def analyze_events(jsonl_file):
    """イベントの統計分析"""
    events = []
    with open(jsonl_file) as f:
        for line in f:
            events.append(json.loads(line))

    # センサー値の統計
    sensor_values = [e.get("sensor_1", 0) for e in events]
    print("Sensor statistics:")
    print(f"  Mean: {statistics.mean(sensor_values):.1f}")
    print(f"  Median: {statistics.median(sensor_values):.1f}")
    print(f"  StdDev: {statistics.stdev(sensor_values):.1f}")
    print(f"  Min: {min(sensor_values)}")
    print(f"  Max: {max(sensor_values)}")

    # イベント発生レート
    timestamps = [e["received_us"] for e in events]
    duration_us = timestamps[-1] - timestamps[0]
    duration_sec = duration_us / 1_000_000
    rate = len(events) / duration_sec
    print(f"\nEvent rate: {rate:.1f} events/second")

# 使用
analyze_events("events.jsonl")

次のステップ