# 機能設計書 13-group_by

## 概要

本ドキュメントは、dplyrパッケージの`group_by()`関数に関する機能設計書である。この関数は、データフレームを指定した変数でグループ化し、後続の操作がグループごとに適用されるようにする機能を提供する。

### 本機能の処理概要

`group_by()`関数は、既存のデータフレームを1つ以上の変数でグループ化し、`grouped_df`クラスのオブジェクトに変換する。グループ化されたデータフレームに対して`summarise()`、`mutate()`、`filter()`などの操作を行うと、各グループに対して個別に処理が適用される。

**業務上の目的・背景**：データ分析において、カテゴリごとの集計や比較は最も基本的かつ重要な操作である。例えば、地域別の売上合計、製品カテゴリ別の平均価格、顧客セグメント別の行動パターンなど、グループ単位での分析は意思決定に不可欠な情報を提供する。`group_by()`は、このようなグループ単位の操作を直感的かつ効率的に実現するための基盤機能を提供する。

**機能の利用シーン**：
- カテゴリ変数ごとの集計（平均、合計、件数など）を行う場合
- グループごとにデータをフィルタリングする場合（例：各グループの上位N件）
- グループ内での計算（例：グループ内順位、累積合計）を行う場合
- 複数の変数の組み合わせによるクロス集計を行う場合
- 計算結果を新しいグループ化列として使用する場合

**主要な処理内容**：
1. `enquos()`で引数をquosureとしてキャプチャ
2. `group_by_prepare()`でグループ化の準備（必要に応じてmutate実行）
3. 式が含まれる場合、暗黙的に`mutate()`を実行して新列を作成
4. `.add = TRUE`の場合、既存グループに追加
5. `grouped_df()`で`grouped_df`クラスのオブジェクトを生成
6. グループは内部的に昇順でソートされる

**関連システム・外部連携**：`ungroup()`でグループ化を解除、`summarise()`/`mutate()`/`filter()`等と連携してグループ単位の操作を実現。グループデータは`group_data()`で取得可能。

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

## 関連画面

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

## 機能種別

データ変換処理（グループ操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| .data | data.frame | Yes | 操作対象のデータフレーム | data.frameまたはその派生クラスであること |
| ... | data-masking | No | グループ化に使用する変数または計算式 | 指定された変数が存在すること |
| .add | logical | No | TRUEの場合、既存グループに追加（デフォルト: FALSE） | 論理値であること |
| .drop | logical | No | ファクターの未使用レベルを除外するか（デフォルト: TRUE） | 論理値であること |

### 入力データソース

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

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| 戻り値 | grouped_df | グループ化されたデータフレーム |

### 出力先

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

### 出力特性

- 戻り値は`grouped_df`クラス（グループが空の場合はtibble）
- データの行と列は変更されない（計算式による新列追加を除く）
- グループ情報は属性として保持される
- グループは内部的に昇順でソートされる（文字列はCロケール）

## 処理フロー

### 処理シーケンス

```
1. UseMethod("group_by")によるメソッドディスパッチ
   └─ .dataの型に応じた適切なメソッドが呼び出される
2. group_by_prepare()でグループ化の準備
   └─ enquos()で引数をキャプチャ
3. 計算式のチェックと処理
   └─ 式が含まれる場合、add_computed_columns()でmutate実行
4. .addオプションの処理
   └─ TRUEの場合、既存グループ変数とunion
5. グループ変数の検証
   └─ 存在しない変数が指定されていないかチェック
6. grouped_df()でグループ化データフレームを生成
   └─ .dropオプションを適用
```

### フローチャート

```mermaid
flowchart TD
    A[開始] --> B[group_by呼び出し]
    B --> C[UseMethodでディスパッチ]
    C --> D[group_by_prepare実行]
    D --> E{計算式あり?}
    E -->|Yes| F[add_computed_columns]
    E -->|No| G[グループ変数名抽出]
    F --> G
    G --> H{.add = TRUE?}
    H -->|Yes| I[既存グループとunion]
    H -->|No| J[新規グループ変数を設定]
    I --> K[変数存在チェック]
    J --> K
    K --> L{グループ変数空?}
    L -->|Yes| M[tibbleを返却]
    L -->|No| N[grouped_df生成]
    N --> O[.drop適用]
    O --> P[結果を返却]
    M --> P
    P --> Q[終了]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-01 | デフォルト上書き | .add=FALSEの場合、既存のグループは上書きされる | .addが未指定またはFALSE |
| BR-02 | グループ追加 | .add=TRUEの場合、既存グループに新しいグループが追加される | .addがTRUE |
| BR-03 | 暗黙的mutate | 計算式が渡された場合、暗黙的にmutate()が実行される | ...に式が含まれる場合 |
| BR-04 | 非グループでの計算 | 暗黙的mutateは常に非グループ化データに対して実行される | 計算式使用時 |
| BR-05 | ファクターレベル | .drop=FALSEの場合、データに存在しないファクターレベルも保持 | .dropがFALSE |
| BR-06 | グループ順序 | グループは内部的に昇順でソートされる | 常に適用 |
| BR-07 | 文字列ロケール | 文字列のグループ化はCロケールで行われる | 文字列でグループ化時 |

### 計算ロジック

**グループ変数の決定**:
```
if (.add == TRUE) {
  group_names = union(既存グループ, 新規指定グループ)
} else {
  group_names = 新規指定グループ
}
```

**暗黙的mutateの判定**:
- 引数に名前が付いている場合 → mutateが必要
- 引数がシンボルでない場合 → mutateが必要
- 引数が`.data$col`または`.data[["col"]]`形式の場合 → mutate不要

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

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

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | 変数未発見 | 存在しない変数名が指定された場合 | "Must group by variables found in `.data`. Column `{column}` is not found." |
| - | 非推奨引数 | 非推奨の`add`引数が使用された場合 | lifecycle::deprecate_stop（.addを使用するよう案内） |
| - | 非推奨引数 | 非推奨の`.dots`引数が使用された場合 | lifecycle::deprecate_stop |

### リトライ仕様

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

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

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

## パフォーマンス要件

- グループ化の計算量はO(n log n)（nは行数）- ソートが必要なため
- メモリ使用量は入力データとグループインデックス情報を保持する分が必要

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

- 本機能は純粋なデータ変換操作であり、特別なセキュリティ考慮事項はない
- グループ化によってデータの内容は変更されない

## 備考

- `group_by()`はdplyrの中核機能の一つ
- 文字列のグループ化順序はCロケールで行われるため、ロケール依存の出力が必要な場合は後で`arrange()`を使用
- dplyr 1.1.0以前は文字列がシステムロケールでソートされていた（`dplyr.legacy_locale`オプションで旧動作を維持可能だが非推奨）

---

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

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

### 推奨読解順序

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

`group_by()`は`grouped_df`クラスのオブジェクトを生成する。グループ情報は属性として保持される。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | grouped-df.R | `R/grouped-df.R` | grouped_dfクラスの構造とグループデータの格納方法 |
| 1-2 | group-data.R | `R/group-data.R` | グループデータへのアクセス方法 |

**読解のコツ**: `grouped_df`は`tbl_df`を継承し、`groups`属性にグループ情報を保持する。グループデータは各グループのキー値と行インデックス（`.rows`）を含むデータフレーム。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | group-by.R | `R/group-by.R` | `group_by()`関数のジェネリック定義（132-139行目） |
| 2-2 | group-by.R | `R/group-by.R` | `group_by.data.frame()`メソッド実装（142-155行目） |

**主要処理フロー**:
1. **132-139行目**: ジェネリック関数定義、デフォルト引数の設定
2. **148-153行目**: `group_by_prepare()`でグループ化準備
3. **154行目**: `grouped_df()`でグループ化データフレーム生成

#### Step 3: 準備処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | group-by.R | `R/group-by.R` | `group_by_prepare()`関数（205-252行目） |
| 3-2 | group-by.R | `R/group-by.R` | `add_computed_columns()`関数（254-283行目） |

**主要処理フロー**:
- **222行目**: `enquos()`で引数をキャプチャ
- **225-229行目**: `add_computed_columns()`で計算式を処理
- **234-236行目**: `.add=TRUE`の場合、既存グループとunion
- **238-245行目**: グループ変数の存在チェック

#### Step 4: 計算式判定ロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | group-by.R | `R/group-by.R` | `quo_is_variable_reference()`関数（285-306行目） |

**主要処理フロー**:
- **286-288行目**: シンボルならmutate不要
- **290-302行目**: `.data$col`や`.data[["col"]]`形式のチェック

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

```
group_by(.data, ..., .add, .drop)
    │
    ├─ UseMethod("group_by")
    │      │
    │      └─ group_by.data.frame()
    │             │
    │             ├─ group_by_prepare()
    │             │      │
    │             │      ├─ enquos() [引数キャプチャ]
    │             │      │
    │             │      ├─ add_computed_columns()
    │             │      │      │
    │             │      │      ├─ quo_is_variable_reference()
    │             │      │      │
    │             │      │      └─ mutate_cols() [計算式処理]
    │             │      │
    │             │      └─ union() [.add=TRUE時]
    │             │
    │             └─ grouped_df()
    │                    │
    │                    └─ new_grouped_df()
```

### データフロー図

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

.data ─────────────────┬─ group_by_prepare() ───────── groups構造体
                       │         │
... (グループ変数) ────┤         ├─ enquos()
                       │         │
.add ──────────────────┤         ├─ add_computed_columns()
                       │         │
.drop ─────────────────┘         └─ union() (.add時)
                                         │
                                         ▼
                                    grouped_df()
                                         │
                                         ▼
                                [grouped_df オブジェクト]
                                    - データ本体
                                    - groups属性（キー + .rows）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| group-by.R | `R/group-by.R` | ソース | group_by/ungroup関数のメイン実装 |
| grouped-df.R | `R/grouped-df.R` | ソース | grouped_dfクラスの定義とコンストラクタ |
| group-data.R | `R/group-data.R` | ソース | グループデータアクセス関数 |
| test-group-by.R | `tests/testthat/test-group-by.R` | テスト | group_byのユニットテスト |
| test-grouped-df.R | `tests/testthat/test-grouped-df.R` | テスト | grouped_dfのユニットテスト |
