データ品質の確認¶
このガイドでは、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")
次のステップ¶
- パラメータ調整 - スレッショルドとpoll_countの最適化
- イベント読み出しの問題 - イベント読み込みの問題解決
- パフォーマンスの問題 - データ取得の高速化