# 機能設計書 17-reframe

## 概要

本ドキュメントは、dplyrパッケージの`reframe()`関数に関する機能設計書である。この関数は、`summarise()`と同様にデータフレームを変換するが、各グループから任意の行数を返すことができる、より柔軟な集計機能を提供する。

### 本機能の処理概要

`reframe()`関数は、データフレームの各グループに関数を適用し、新しいデータフレームを生成する。`summarise()`が各グループを1行に集約するのに対し、`reframe()`は各グループから任意の行数を返すことができる。出力は常に非グループ化されたデータフレームとなる。

**業務上の目的・背景**：データ分析において、グループごとに複数の値を返す必要がある場面がある。例えば、各グループの分位数、上位N件の値、集合演算の結果などである。`summarise()`は長さ1の制約があるため、このようなケースには対応できない。`reframe()`は、この制約を取り除き、より柔軟なグループ変換を可能にする。

**機能の利用シーン**：
- 各グループの分位数（複数値）を計算したい場合
- グループごとに異なる行数の結果を返したい場合
- 集合演算（intersect、union等）の結果を返したい場合
- グループ内のデータを展開したい場合
- 複雑な関数が複数行のデータフレームを返す場合

**主要な処理内容**：
1. `UseMethod()`によるメソッドディスパッチ
2. `compute_by()`でグループ情報を取得
3. `summarise_cols()`で各式を評価（reframeモード）
4. グループ内の式を水平方向にリサイクル（同じグループ内で長さを揃える）
5. `summarise_build()`で出力データフレームを構築
6. グループキーを結果の行数分繰り返す
7. 常に非グループ化されたデータフレームを返す

**関連システム・外部連携**：`summarise()`と内部実装を共有。`group_by()`または`.by`引数でグループ化と連携。`across()`で複数列に同じ関数を適用可能。

**権限による制御**：本機能はRパッケージの一部であり、特定の権限制御は存在しない。関数を呼び出す全てのユーザーが同じ機能を利用可能である。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| - | 該当なし | - | 本機能はRライブラリ（dplyr）のため画面は存在しない |

## 機能種別

データ変換処理（集計）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| .data | data.frame | Yes | 操作対象のデータフレーム | data.frameまたはその派生クラスであること |
| ... | data-masking | No | 名前付きの変換式 | 任意の長さの値を返すことが可能 |
| .by | tidy-select | No | 一時的なグループ化変数 | grouped_df/rowwise_dfでは使用不可 |

### 入力データソース

- Rセッション内のdata.frame、tibble、grouped_df、rowwise_df、またはその派生クラスのオブジェクト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | tibble/data.frame | 変換されたデータフレーム（常に非グループ化） |

### 出力先

- Rセッション内のオブジェクトとして返却

### 出力特性

- 行はグループキーの繰り返しと式の結果から生成
- 列はグループ変数と式の結果の組み合わせ
- 出力は常に非グループ化（ungrouped）
- データフレーム属性は保持されない

## 処理フロー

### 処理シーケンス

```
1. UseMethod("reframe")によるメソッドディスパッチ
   └─ .dataの型に応じた適切なメソッドが呼び出される
2. compute_by()でグループ情報を取得
3. summarise_cols()で変換式を評価（reframeモード）
   ├─ DataMaskを作成
   ├─ 各式を評価（長さ制約なし）
   └─ グループ内で水平リサイクル
4. summarise_build()で出力を構築
   └─ グループキーを結果行数分繰り返す
5. 非グループ化されたデータフレームを返却
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[reframe呼び出し]
    B --> C[UseMethodでディスパッチ]
    C --> D[compute_byでグループ取得]
    D --> E[summarise_colsで式評価]
    E --> F{同グループ内の長さが異なる?}
    F -->|Yes| G[水平リサイクル]
    F -->|No| H[そのまま進行]
    G --> I{リサイクル可能?}
    I -->|No| J[エラー: サイズ不整合]
    I -->|Yes| K[summarise_buildで出力構築]
    H --> K
    K --> L[グループキーを繰り返し]
    L --> M[非グループ化データフレームを返却]
    M --> N[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | 任意長さ許可 | 各式は任意の長さの値を返すことができる | 常に適用 |
| BR-02 | グループ内リサイクル | 同じグループ内の式は互いにリサイクルされる | 複数式が異なる長さの場合 |
| BR-03 | リサイクル制約 | リサイクルは長さ1または同じ長さの間でのみ可能 | グループ内で複数の長さが存在 |
| BR-04 | 常に非グループ | 出力は常に非グループ化されたデータフレーム | 常に適用 |
| BR-05 | キー繰り返し | グループキーは結果の行数分繰り返される | 常に適用 |

### 計算ロジック

**水平リサイクル**:
```
# 同じグループ内で、長さの異なる結果をリサイクル
# 例: グループ1で col_a が長さ3、col_b が長さ1の場合
# col_b は長さ3にリサイクルされる

if (size_a == 1) {
  result_a = rep(result_a, size_b)
} else if (size_b == 1) {
  result_b = rep(result_b, size_a)
} else if (size_a != size_b) {
  error("リサイクル不可")
}
```

**グループキーの繰り返し**:
```
out = vec_rep_each(group_keys, group_sizes)
```

## データベース操作仕様

該当なし（本機能はインメモリのデータフレーム操作のみ）

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | サイズエラー | 同グループ内で互換性のない長さ（1でも同一でもない）の場合 | "`{name}` must be size {expected_size} or 1, not {actual_size}. An earlier column had size {expected_size}." |

### リトライ仕様

該当なし（インタラクティブな関数呼び出しのため）

## トランザクション仕様

該当なし（本機能はデータベーストランザクションを使用しない）

## パフォーマンス要件

- 計算量はO(n * m)（n=行数、m=グループ数）に加え、結果の行数に依存
- 出力行数が入力より大幅に増加する場合はメモリ使用量に注意

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

- 本機能は純粋なデータ変換操作であり、特別なセキュリティ考慮事項はない

## 備考

- `reframe()`はdplyr 1.1.0で導入された
- 名前の由来: tibbleの`enframe()`（ベクトル→データフレーム）、`deframe()`（データフレーム→ベクトル）と概念的に関連
- `summarise()`より柔軟だが、出力が予測しにくくなる可能性があるため、用途を明確にして使用
- 常に非グループ化されるため、後続のグループ操作が必要な場合は再度`group_by()`が必要

---

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

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

### 推奨読解順序

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

`reframe()`は`summarise()`と内部実装を共有しており、出力構造は同じだが、行数の制約が異なる。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | reframe.R | `R/reframe.R` | reframeの戻り値構造（34-42行目） |

**読解のコツ**: `reframe()`は`summarise_cols()`を"reframe"モードで呼び出し、長さ1制約を無効化する。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | reframe.R | `R/reframe.R` | `reframe()`関数のジェネリック定義（108-110行目） |
| 2-2 | reframe.R | `R/reframe.R` | `reframe.data.frame()`（113-126行目） |

**主要処理フロー**:
1. **114行目**: `compute_by()`でグループ情報取得
2. **116行目**: `summarise_cols()`を"reframe"モードで呼び出し
3. **117行目**: `summarise_build()`で出力構築
4. **119-122行目**: 必要に応じてdata.frameにダウンキャスト

#### Step 3: reframe固有の処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | summarise.R | `R/summarise.R` | `summarise_cols()`のreframeモード（287-304行目） |

**主要処理フロー（summarise_colsのreframeモード）**:
- **287-295行目**: グループ内の水平リサイクル処理
- **290-295行目**: `dplyr_reframe_recycle_horizontally_in_place()`でリサイクル実行
- **378-384行目**: `summarise_build()`でキーを繰り返し

#### Step 4: エラーメッセージを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | summarise.R | `R/summarise.R` | `summarise_bullets.dplyr:::reframe_incompatible_size`（424-444行目） |

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

```
reframe(.data, ..., .by)
    │
    ├─ UseMethod("reframe")
    │      │
    │      └─ reframe.data.frame()
    │             │
    │             ├─ compute_by() [グループ取得]
    │             │
    │             ├─ summarise_cols(..., verb = "reframe")
    │             │      │
    │             │      ├─ DataMask$new()
    │             │      │
    │             │      ├─ summarise_eval_one() [式評価]
    │             │      │
    │             │      └─ dplyr_reframe_recycle_horizontally_in_place()
    │             │             [水平リサイクル]
    │             │
    │             └─ summarise_build(..., verb = "reframe")
    │                    │
    │                    └─ vec_rep_each() [キー繰り返し]
```

### データフロー図

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

.data ─────────────────┬─ compute_by() ────────────────── グループ情報(by)
                       │         │
... (変換式) ──────────┤         │
                       │         ▼
.by (オプション) ──────┘   summarise_cols(verb="reframe")
                                 │
                                 ├─ 式の評価（長さ制約なし）
                                 │
                                 └─ 水平リサイクル
                                         │
                                         ▼
                                 summarise_build(verb="reframe")
                                         │
                                         ├─ vec_rep_each(keys, sizes)
                                         │
                                         └─ 列を結合
                                                │
                                                ▼
                                [非グループ化data.frame/tibble]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| reframe.R | `R/reframe.R` | ソース | reframe関数のエントリーポイント |
| summarise.R | `R/summarise.R` | ソース | 内部実装（summarise_cols、summarise_build） |
| data-mask.R | `R/data-mask.R` | ソース | DataMaskクラス（式評価の基盤） |
| test-reframe.R | `tests/testthat/test-reframe.R` | テスト | reframeのユニットテスト |
