# 帳票設計書 20-ministat - 統計比較レポート

## 概要

本ドキュメントは、FreeBSD の `ministat` コマンドが出力する統計比較レポートの設計仕様を記述する。

### 本帳票の処理概要

ministat は、複数のデータセットの統計的比較を行うユーティリティである。データセットのサマリー統計量（N, Min, Max, Median, Avg, Stddev）を算出し、ASCII アートによるデータ分布プロットを表示し、Student の t 検定により2つのデータセット間に統計的に有意な差があるかを判定する。

**業務上の目的・背景**：パフォーマンスベンチマーク結果の比較、設定変更前後の効果測定、A/Bテストの統計的検証などの場面で使用される。「差が有意かどうか」を統計学的に検証することで、ノイズと真の差を区別する。

**帳票の利用シーン**：ベンチマーク結果の比較分析、パフォーマンスチューニングの効果検証、回帰テスト結果の差異判定、科学的実験データの比較などの場面で利用される。

**主要な出力内容**：
1. データセット名の凡例（シンボル対応）
2. ASCII アートによるデータ分布プロット（ヒストグラム + 平均/中央値/標準偏差バー）
3. サマリー統計量テーブル（N, Min, Max, Median, Avg, Stddev）
4. Student の t 検定結果（有意差の有無、差分、信頼区間、パーセンテージ）

**帳票の出力タイミング**：ユーザーがコマンドラインから `ministat` コマンドを実行した際に標準出力に出力される。

**帳票の利用者**：パフォーマンスエンジニア、システム管理者、研究者

## 帳票種別

分析レポート（統計比較分析）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| N/A | コマンドラインインターフェース | N/A | `ministat [file1] [file2] ...` コマンド実行 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | テキスト（標準出力） |
| 用紙サイズ | N/A |
| 向き | N/A |
| ファイル名 | N/A（標準出力） |
| 出力方法 | 標準出力（stdout） |
| 文字コード | ASCII |

## 帳票レイアウト

### レイアウト概要

凡例、ASCIIプロット、統計テーブル、t検定結果で構成される。

```
┌──────────────────────────────────────────────────────────────┐
│ x file1                                                        │
│ + file2                                                        │
│ +--------------------------------------------------------------------------+│
│ |                                              x                           |│
│ |                                x          x  x     x                     |│
│ |        +              x  x    xx  x  x x  x  x  x  x  x                |│
│ |  +  + ++  + +  + + +  x  x   xx  x  x xx xx  x  x  x  x  x            |│
│ |          |___________MA______________|                                    |│
│ |              |______________M__A_________________|                        |│
│ +--------------------------------------------------------------------------+│
│     N           Min           Max        Median           Avg        Stddev │
│ x  50     0.0045678     0.0098765     0.0067890     0.0069012     0.0012345│
│ +  50     0.0034567     0.0087654     0.0056789     0.0058901     0.0011234│
│ Difference at 95.0% confidence                                              │
│ 	0.00101111 +/- 0.000456789                                             │
│ 	17.16% +/- 7.75%                                                      │
│ 	(Student's t, pooled s = 0.0011789)                                    │
└──────────────────────────────────────────────────────────────┘
```

### 凡例部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | シンボル | データセットのプロット記号 | symbol[]配列 | 1文字（x, +, *, %, #, @, O） |
| 2 | ファイル名 | データセットの元ファイル名 | ds[i]->name | 文字列 |

### ASCIIプロット部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | データポイント | 各値のヒストグラム表示 | ds[i]->points[] | symbol[i+1]文字 |
| 2 | 平均マーカー | 平均値の位置 | Avg(ds) | "A" |
| 3 | 中央値マーカー | 中央値の位置 | Median(ds) | "M" |
| 4 | 標準偏差バー | 平均+/-標準偏差の範囲 | Avg +/- Stddev | "\|___...___|" |

### 統計テーブル部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | N | データポイント数 | ds->n | "%3zu" |
| 2 | Min | 最小値 | ds->points[0]（ソート済み） | "%13.8g" |
| 3 | Max | 最大値 | ds->points[n-1] | "%13.8g" |
| 4 | Median | 中央値 | (points[m] + points[m-1])/2 or points[m] | "%13.8g" |
| 5 | Avg | 平均値 | sy / n | "%13.8g" |
| 6 | Stddev | 標準偏差 | sqrt(Var) | "%13.8g" |

### t検定結果部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | 判定結果 | 有意差の有無 | \|d\| > e の判定 | "Difference at X% confidence" / "No difference proven at X% confidence" |
| 2 | 差分 | 平均値の差 +/- 誤差 | Avg(ds) - Avg(rs) +/- t*s | "%g +/- %g" |
| 3 | パーセンテージ | 差分の割合 +/- 誤差割合 | d*100/Avg(rs) +/- re*100/Avg(rs) | "%g%% +/- %g%%" |
| 4 | プールド標準偏差 | プールされた標準偏差 | sqrt(spool) | "(Student's t, pooled s = %g)" |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| データポイント数 | 各データセットに最低3ポイント必要 | Yes（行517-521） |
| データセット数 | 最大MAX_DS-1=7データセット | Yes（行637-638） |
| コメント行 | '#'で始まる行はスキップ | 自動 |

### ソート順

各データセット内はqsortで昇順ソート（行522）

## データベース参照仕様

### 参照テーブル一覧

| テーブル名 | 用途 | 結合条件 |
|-----------|------|---------|
| 入力ファイル | データポイント読み取り | fgets() + strtok() + strtod() |

### テーブル別参照項目詳細

#### データセット（struct dataset）

| 参照項目（カラム名） | 帳票項目との対応 | 取得条件 | 備考 |
|-------------------|----------------|---------|------|
| points[] | 全データポイント | ReadSet() | qsortでソート済み |
| n | データポイント数 | AddPoint() | |
| sy | 合計値 | AddPoint() | Avg計算用 |
| syy | 偏差二乗和 | Var() | 遅延計算（NAN初期値） |
| name | データセット名 | ReadSet() | ファイル名 |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 平均値 (Avg) | sy / n | なし | 行193-197 |
| 中央値 (Median) | n偶数: (points[m] + points[m-1])/2, n奇数: points[m] | なし | 行199-207 |
| 分散 (Var) | SUM((points[z] - Avg)^2) / (n-1) | なし | 行209-222、不偏分散 |
| 標準偏差 (Stddev) | sqrt(Var) | なし | 行224-229 |
| プールド分散 (spool) | ((n1-1)*Var1 + (n2-1)*Var2) / (n1+n2-2) | なし | 行259-261 |
| 標準誤差 (s) | spool * sqrt(1/n1 + 1/n2) | なし | 行262 |
| 平均差 (d) | Avg(ds) - Avg(rs) | なし | 行263 |
| 誤差マージン (e) | t * s | なし | 行264 |
| 相対誤差 (re) | t * sqrt(複合分散式) | なし | 行266-269 |
| t値 | student[z][confidx]（テーブル参照） | なし | z = n1+n2-2、行255-258 |

### Student t分布テーブル

| 項目 | 内容 |
|-----|------|
| 自由度範囲 | 1～100 + 無限大（行32-134） |
| 信頼水準 | 80%, 90%, 95%, 98%, 99%, 99.5%（行31: NCONF=6） |
| デフォルト信頼水準 | 95%（ci=2, 行627-628） |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[ministatコマンド実行] --> B[オプション解析]
    B --> C[Capsicumサンドボックス設定]
    C --> D[ReadSet: 各ファイルからデータ読取り]
    D --> E[qsort: データポイントソート]
    E --> F{-n/-Aフラグ?}
    F -->|プロットあり| G[SetupPlot/DimPlot/PlotSet/DumpPlot]
    F -->|プロットなし| H[統計テーブルのみ]
    G --> I[VitalsHead: ヘッダー出力]
    H --> I
    I --> J[Vitals: 各データセットの統計量出力]
    J --> K{2つ以上のデータセット?}
    K -->|Yes| L[Relative: t検定実行・結果出力]
    K -->|No| M[終了]
    L --> M
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| ファイルオープン失敗 | fopen()失敗 | "Cannot open %s" | err(2, ...) |
| データ不足 | データポイント数 < 3 | "Dataset %s must contain at least 3 data points" | exit(2) |
| データセット数超過 | argc > MAX_DS-1（7） | "Too many datasets." | usage() + exit(2) |
| 無効なデータ | strtod()でパース失敗 | "Invalid data on line %d in %s" | errx(2, ...) |
| 無効な信頼水準 | サポート外の値 | "No support for confidence level" | usage() + exit(2) |
| 無効なカラム番号 | column <= 0 | "Column number should be positive." | usage() + exit(2) |
| Capsicumエラー | caph_enter()失敗 | "unable to enter capability mode" | err(2, ...) |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 初期100000ポイント/データセット（動的拡張、行154） |
| 目標出力時間 | データサイズに依存（qsortがボトルネック） |
| 同時出力数上限 | 最大7データセット（MAX_DS-1） |

## セキュリティ考慮事項

- Capsicumサンドボックス対応（行651-661: caph_limit_stdio, caph_limit_stream, caph_enter）
- ファイルオープン後にCapability modeに入ることで、サンドボックス内での処理を保証
- メモリアロケーション失敗時はassertで停止（行153, 156, 169等）

## 備考

- MAX_DS=8（行136）、シンボル配列: {' ', 'x', '+', '*', '%', '#', '@', 'O'}（行137）
- NSTUDENT=100、NCONF=6の事前計算済みt分布テーブルを内蔵（行29-134）
- -A オプションでプロット非表示（統計量のみ出力）
- -n オプションでサマリー統計のみ（プロット・t検定なし）
- -q オプションでヘッダー・データセット名を非表示
- -s オプションで平均/中央値/標準偏差バーを分離表示
- -C オプションでカラム番号指定（デフォルト1）
- -c オプションで信頼水準指定（デフォルト95%）
- -d オプションでデリミタ指定（デフォルト " \t"）
- -w オプションでプロット幅指定（デフォルト74またはターミナル幅-2）
- プロット幅はターミナル幅を自動検出（行569-577: TIOCGWINSZ / COLUMNS環境変数）
- 標準入力からの読み取りも可能（ファイル名"-"または引数なし）
- 各データセットの最小ポイント数は3（行517）
- 分散計算は遅延評価（Var()初回呼出し時にsyyを計算、行215-219）

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | ministat.c | `usr.bin/ministat/ministat.c` | struct dataset（行139-145）：points[], n, sy, syy, name |
| 1-2 | ministat.c | `usr.bin/ministat/ministat.c` | struct plot（行282-294）：min, max, span, width, height, data, bar |
| 1-3 | ministat.c | `usr.bin/ministat/ministat.c` | student[]テーブル（行32-134）：NSTUDENT=100, NCONF=6 |

**読解のコツ**: datasetは動的配列で、初期100000ポイントを確保し、不足時に4倍拡張する。syy（偏差二乗和）はNAN初期値で遅延計算される。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ministat.c | `usr.bin/ministat/ministat.c` | main()（行551-691）：オプション解析、Capsicum、データ読取り、出力 |

**主要処理フロー**:
1. **行569-577**: ターミナル幅自動検出（TIOCGWINSZ / COLUMNS）
2. **行580-626**: getopt("AC:c:d:snqw:")によるオプション解析
3. **行632-648**: ファイルオープン（stdin対応）
4. **行651-661**: Capsicumサンドボックス設定
5. **行663-667**: ReadSet()でデータセット読取り
6. **行669-672**: データセット名の凡例出力（-q無効時）
7. **行674-680**: プロット描画（-n/-A無効時）
8. **行682-689**: 統計テーブル出力 + t検定（Relative）

#### Step 3: データ読取りとソートを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ministat.c | `usr.bin/ministat/ministat.c` | ReadSet()（行483-523）：fgets, strtok, strtod, AddPoint, qsort |
| 3-2 | ministat.c | `usr.bin/ministat/ministat.c` | AddPoint()（行161-176）：動的配列追加、sy累積 |
| 3-3 | ministat.c | `usr.bin/ministat/ministat.c` | NewSet()（行147-159）：初期100000ポイント確保 |

#### Step 4: 統計計算を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ministat.c | `usr.bin/ministat/ministat.c` | Avg()（行192-197）、Median()（行199-207）、Var()（行209-222）、Stddev()（行224-229） |
| 4-2 | ministat.c | `usr.bin/ministat/ministat.c` | Relative()（行247-280）：t検定実装（プールド分散、自由度、t値テーブル参照） |

#### Step 5: プロット描画を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | ministat.c | `usr.bin/ministat/ministat.c` | SetupPlot()（行298-312）、DimPlot()（行330-336）：プロット範囲決定 |
| 5-2 | ministat.c | `usr.bin/ministat/ministat.c` | PlotSet()（行338-417）：データポイント配置、A/M/標準偏差バー描画 |
| 5-3 | ministat.c | `usr.bin/ministat/ministat.c` | DumpPlot()（行419-467）：プロット出力 |

### プログラム呼び出し階層図

```
main() [ministat.c:551]
    |
    +-- getopt() [オプション解析]
    +-- fopen() [ファイルオープン]
    +-- caph_limit_stdio() / caph_enter() [Capsicum]
    |
    +-- ReadSet() [ministat.c:483]
    |   +-- NewSet() [ministat.c:147]
    |   +-- fgets() / strtok() / strtod() [データパース]
    |   +-- AddPoint() [ministat.c:161]
    |   +-- qsort(dbl_cmp) [ソート]
    |
    +-- SetupPlot() [ministat.c:298]
    +-- DimPlot() [ministat.c:330]
    |   +-- AdjPlot() [ministat.c:314]
    +-- PlotSet() [ministat.c:338]
    +-- DumpPlot() [ministat.c:419]
    |
    +-- VitalsHead() [ministat.c:231]
    +-- Vitals() [ministat.c:238]
    |   +-- Min() / Max() / Median() / Avg() / Stddev()
    |
    +-- Relative() [ministat.c:247]
        +-- Var() [ministat.c:209]
        +-- Avg() [ministat.c:192]
        +-- student[][] [t分布テーブル参照]
```

### データフロー図

```
[入力]                        [処理]                         [出力]

入力ファイル ────────> ReadSet() ──────────> qsort()
(テキスト)            [データパース]         [昇順ソート]
                            |
                      dataset構造体
                      (points[], n, sy)
                            |
                      +-----+-----+
                      |           |
                DimPlot/PlotSet  Vitals/Relative
                [プロット計算]   [統計計算]
                      |           |
                 DumpPlot()  printf()           ──> stdout
                 [ASCII描画] [統計テーブル]
                             [t検定結果]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ministat.c | `usr.bin/ministat/ministat.c` | ソース | メインプログラム（692行、自己完結型） |
| Makefile | `usr.bin/ministat/Makefile` | ビルド | ビルド設定（libm依存） |
