## はじめに
データ分析では、連続変数を群に分けて解析することが頻繁にあります。群分けによって、より理解しやすい形で結果を示したり、群間比較を行ったりすることができます。
BMI(Body Mass Index)を例として、連続変数を群分けする5つの異なる方法を実例とともに紹介し、それぞれの特徴について解説します。
## サンプルデータについて
BMIとヘモグロビン濃度の関係を例に、以下の関係性を持つサンプルデータを作成しました:

上図は、BMIとヘモグロビンの関係を示した散布図です。明確な正の相関関係が確認でき、BMIが高くなるほどヘモグロビン濃度が上昇する傾向が見られます。
## Rコードの実行
以下のRコードを実行することで、すべての分析と可視化を行うことができます:
```r
# Rファイルの実行
source("bmi_analysis.R")
```
## 5つの分類方法
### 1. 均等3群分割(Tertile:三分位)
まずは、データを人数が等しくなるように3つのグループに分割してみましょう。
```r
df$BMI_tertile <- cut(df$BMI,
breaks = quantile(df$BMI, probs = c(0, 1/3, 2/3, 1)),
labels = c("Low", "Middle", "High"),
include.lowest = TRUE)
```
**特徴:**
- 各群の人数が等しい(n=16, 17, 17)
- 統計学的検出力が均等
- カットポイントがデータ依存的

### 2. 均等4群分割(Quartile:四分位)
おなじく4つのグループに分割する方法です。
```r
df$BMI_quartile <- cut(df$BMI,
breaks = quantile(df$BMI, probs = c(0, 0.25, 0.5, 0.75, 1)),
labels = c("Q1", "Q2", "Q3", "Q4"),
include.lowest = TRUE)
```

### 3. 均等5群分割(Quintile:五分位)
おなくじ5つのグループに分割する方法です。
```r
df$BMI_quintile <- cut(df$BMI,
breaks = quantile(df$BMI, probs = c(0, 0.2, 0.4, 0.6, 0.8, 1)),
labels = c("Q1", "Q2", "Q3", "Q4", "Q5"),
include.lowest = TRUE)
```
各群のサンプルサイズが小さくなります。

### 4. 等間隔分割(2kg/m²ずつ)
BMI値を一定の間隔で区切る方法です。
```r
df$BMI_2step <- cut(df$BMI,
breaks = c(0, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, Inf),
labels = c("<20", "20-22", "22-24", "24-26", "26-28", "28-30",
"30-32", "32-34", "34-36", "36-38", "38-40", "≥40"),
include.lowest = TRUE, right = FALSE)
```
**特徴:**
- カットポイントが客観的で予め決定
- 群間での人数の違いが大きい場合がある
- 臨床的に意味のある間隔を設定可能

### 5. WHO基準での分類
世界保健機関(WHO)の標準的なBMI分類を使用する方法です。
```r
df$BMI_classification <- cut(df$BMI,
breaks = c(0, 18.5, 25, 30, 35, 40, Inf),
labels = c("Underweight (<18.5)",
"Normal (18.5-25)",
"Overweight (25-30)",
"Obesity Class 1 (30-35)",
"Obesity Class 2 (35-40)",
"Obesity Class 3 (≥40)"),
include.lowest = TRUE, right = FALSE)
```
**特徴:**
- 国際的に標準化された分類
- 臨床的意義が明確
- 他研究との比較が容易
- 群間で人数の差が生じる場合がある

## 結果の比較
各分類方法による結果を比較すると、以下のことが分かります:
1. **Tertile/Quartile/Quintile**: データ駆動的な分割により、階段状の関係を検出
2. **2-step grouping**: 等間隔分割により、BMI 30付近での明確な変化点を特定
3. **WHO分類**: 標準的分類により、臨床的に意味のある群間差を確認
## 分位点(Quantile)について
**重要な概念の整理**
- **分位点(Quantile)**: データを特定の割合で分割する点そのもの
- 例:四分位点 = 25%, 50%, 75%の位置の値
- **分位群(Quantile groups)**: 分位点によって作られるグループ
- 例:第1四分位群、第2四分位群など
よく見られる誤用:
❌ 「1st tertile の群は・・・」(tertileは分位点を指す)
分位点は境界値を、分位群は実際の分析単位を指します。
## どの分類方法を選ぶべきか?
### 客観性の重要性
どの方法が「正しい」ということはありませんが、**客観的な分類**を行うことが重要です:
#### 推奨される方法:
1. **標準的分類の使用**:WHO基準など、国際的に認められた分類
2. **事前の基準設定**:分析前にカットポイントを決定
3. **臨床的意義の考慮**:医学的に意味のある境界値の使用
#### 避けるべき方法:
1. **p-hackingにつながる恣意的な分類**
2. **根拠のない分割数の選択**
### 実際の分析では
手戻りを恐れずに、色々な分割方法を試してみることをお勧めします。データの性質や研究の目的によって、最適な分類方法は変わります。複数の方法で分析し、結果を比較検討することで、データの特徴をより深く理解できるでしょう。
## まとめ
連続変数の群分けには様々な方法があり、それぞれ異なる特徴を持っています。どの方法が「正しい」ということはありませんが、客観的な分類を行うことが重要です。
## 全体のRコード
以下が今回の分析で使用したRコードの全体です:
```r
# BMI分類分析 - 初学者向け記事用コード
# R version
# 必要なライブラリの読み込み
library(ggplot2)
library(dplyr)
library(gridExtra)
# 再現可能性のためのシード設定
set.seed(42)
# BMIとヘモグロビンのサンプルデータ作成 (n=50)
# OSAS(閉塞性睡眠時無呼吸症候群)を想定:
# BMI 25以上でヘモグロビンが少し増加、BMI 30以上で結構増加
# 手動でデータを作成(記事用の固定データ)
# より現実的なパターン:BMI 25から軽度上昇、BMI 30からぐんと上昇
bmi_values <- c(
19.2, 20.1, 20.8, 21.3, 21.9, 22.4, 22.8, 23.2, 23.6, 24.1,
24.5, 24.9, 25.3, 25.7, 26.1, 26.5, 26.9, 27.3, 27.7, 28.1,
28.5, 28.9, 29.3, 29.7, 30.1, 30.5, 30.9, 31.3, 31.7, 32.1,
32.5, 32.9, 33.3, 33.7, 34.1, 34.5, 34.9, 35.3, 35.7, 36.1,
36.5, 36.9, 37.3, 37.7, 38.1, 38.5, 38.9, 39.3, 39.7, 40.1
)
# より現実的なヘモグロビンパターン
hemoglobin_values <- c(
# BMI <25: 基準範囲内(13.0-14.5)でややバラツキ
13.1, 13.4, 13.2, 13.8, 13.5, 13.7, 13.3, 14.1, 13.9, 14.2,
14.0, 13.6,
# BMI 25-30: 軽度上昇傾向(14.0-15.5)
14.3, 14.8, 14.6, 15.0, 14.9, 15.2, 15.1, 15.4,
15.3, 15.0, 15.5, 15.2,
# BMI 30以上: 明確な上昇(16.0-18.5)
16.2, 16.8, 16.5, 17.1, 16.9, 17.4,
17.2, 17.8, 17.5, 18.0, 17.7, 18.3, 18.1, 18.5, 18.2, 18.7,
18.4, 18.9, 18.6, 19.0, 18.8, 19.2, 19.1, 19.4, 19.3, 19.5
)
# データフレーム作成
df <- data.frame(
BMI = bmi_values,
Hemoglobin = hemoglobin_values
)
# データの基本情報表示
cat("Sample Data (first 10 rows):\n")
print(head(df, 10))
cat(paste("\nData shape:", nrow(df), "rows x", ncol(df), "columns\n"))
cat(paste("BMI range:", round(min(df$BMI), 1), "-", round(max(df$BMI), 1), "\n"))
cat(paste("Hemoglobin range:", round(min(df$Hemoglobin), 1), "-", round(max(df$Hemoglobin), 1), "\n"))
# =============================================================================
# 5つの分類方法
# =============================================================================
# 1. BMIを均等に3群に分ける方法(tertile:三分位)
df$BMI_tertile <- cut(df$BMI,
breaks = quantile(df$BMI, probs = c(0, 1/3, 2/3, 1)),
labels = c("Low", "Middle", "High"),
include.lowest = TRUE)
# 2. BMIを均等に4群に分ける方法(quartile:四分位)
df$BMI_quartile <- cut(df$BMI,
breaks = quantile(df$BMI, probs = c(0, 0.25, 0.5, 0.75, 1)),
labels = c("Q1", "Q2", "Q3", "Q4"),
include.lowest = TRUE)
# 3. BMIを均等に5群に分ける方法(quintile:五分位)
df$BMI_quintile <- cut(df$BMI,
breaks = quantile(df$BMI, probs = c(0, 0.2, 0.4, 0.6, 0.8, 1)),
labels = c("Q1", "Q2", "Q3", "Q4", "Q5"),
include.lowest = TRUE)
# 4. BMI 2ずつ区切って分ける方法
df$BMI_2step <- cut(df$BMI,
breaks = c(0, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, Inf),
labels = c("<20", "20-22", "22-24", "24-26", "26-28", "28-30",
"30-32", "32-34", "34-36", "36-38", "38-40", "≥40"),
include.lowest = TRUE, right = FALSE)
# 5. BMI分類に従う方法(WHO基準)
df$BMI_classification <- cut(df$BMI,
breaks = c(0, 18.5, 25, 30, 35, 40, Inf),
labels = c("Underweight (<18.5)",
"Normal (18.5-25)",
"Overweight (25-30)",
"Obesity Class 1 (30-35)",
"Obesity Class 2 (35-40)",
"Obesity Class 3 (≥40)"),
include.lowest = TRUE, right = FALSE)
# =============================================================================
# 各分類方法の分布確認
# =============================================================================
cat("\n=== 分類方法別の分布 ===\n")
cat("\n1. Tertile (3群):\n")
print(table(df$BMI_tertile))
cat("\n2. Quartile (4群):\n")
print(table(df$BMI_quartile))
cat("\n3. Quintile (5群):\n")
print(table(df$BMI_quintile))
cat("\n4. 2-step grouping:\n")
print(table(df$BMI_2step))
cat("\n5. BMI classification:\n")
print(table(df$BMI_classification))
# =============================================================================
# 統計情報の計算
# =============================================================================
cat("\n=== 各分類方法によるヘモグロビン平均値と標準偏差 ===\n")
cat("\n1. Tertile:\n")
tertile_stats <- df %>%
group_by(BMI_tertile) %>%
summarise(
mean = round(mean(Hemoglobin), 2),
sd = round(sd(Hemoglobin), 2),
count = n(),
.groups = 'drop'
)
print(tertile_stats)
cat("\n2. Quartile:\n")
quartile_stats <- df %>%
group_by(BMI_quartile) %>%
summarise(
mean = round(mean(Hemoglobin), 2),
sd = round(sd(Hemoglobin), 2),
count = n(),
.groups = 'drop'
)
print(quartile_stats)
cat("\n3. Quintile:\n")
quintile_stats <- df %>%
group_by(BMI_quintile) %>%
summarise(
mean = round(mean(Hemoglobin), 2),
sd = round(sd(Hemoglobin), 2),
count = n(),
.groups = 'drop'
)
print(quintile_stats)
cat("\n4. 2-step grouping:\n")
step2_stats <- df %>%
filter(!is.na(BMI_2step)) %>%
group_by(BMI_2step) %>%
summarise(
mean = round(mean(Hemoglobin), 2),
sd = round(sd(Hemoglobin), 2),
count = n(),
.groups = 'drop'
)
print(step2_stats)
cat("\n5. BMI classification:\n")
class_stats <- df %>%
filter(!is.na(BMI_classification)) %>%
group_by(BMI_classification) %>%
summarise(
mean = round(mean(Hemoglobin), 2),
sd = round(sd(Hemoglobin), 2),
count = n(),
.groups = 'drop'
)
print(class_stats)
# =============================================================================
# 可視化
# =============================================================================
# 散布図
p_scatter <- ggplot(df, aes(x = BMI, y = Hemoglobin)) +
geom_point(color = "skyblue", alpha = 0.7, size = 2) +
geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
labs(title = "BMI vs Hemoglobin Scatter Plot",
x = "BMI (kg/m²)",
y = "Hemoglobin (g/dL)") +
theme_minimal() +
theme(plot.title = element_text(hjust = 0.5))
# 1. Tertile
p_tertile <- df %>%
group_by(BMI_tertile) %>%
summarise(mean_hb = mean(Hemoglobin), .groups = 'drop') %>%
ggplot(aes(x = BMI_tertile, y = mean_hb, fill = BMI_tertile)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = c("lightcoral", "lightyellow", "lightgreen")) +
labs(title = "1. Tertile (3 groups)",
x = "BMI Group",
y = "Mean Hemoglobin (g/dL)") +
theme_minimal() +
theme(legend.position = "none", plot.title = element_text(hjust = 0.5))
# 2. Quartile
p_quartile <- df %>%
group_by(BMI_quartile) %>%
summarise(mean_hb = mean(Hemoglobin), .groups = 'drop') %>%
ggplot(aes(x = BMI_quartile, y = mean_hb, fill = BMI_quartile)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = c("lightcoral", "lightyellow", "lightgreen", "lightblue")) +
labs(title = "2. Quartile (4 groups)",
x = "BMI Group",
y = "Mean Hemoglobin (g/dL)") +
theme_minimal() +
theme(legend.position = "none", plot.title = element_text(hjust = 0.5))
# 3. Quintile
p_quintile <- df %>%
group_by(BMI_quintile) %>%
summarise(mean_hb = mean(Hemoglobin), .groups = 'drop') %>%
ggplot(aes(x = BMI_quintile, y = mean_hb, fill = BMI_quintile)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = c("lightcoral", "lightyellow", "lightgreen", "lightblue", "lightpink")) +
labs(title = "3. Quintile (5 groups)",
x = "BMI Group",
y = "Mean Hemoglobin (g/dL)") +
theme_minimal() +
theme(legend.position = "none", plot.title = element_text(hjust = 0.5))
# 4. 2-step grouping
p_2step <- df %>%
filter(!is.na(BMI_2step)) %>%
group_by(BMI_2step) %>%
summarise(mean_hb = mean(Hemoglobin), .groups = 'drop') %>%
ggplot(aes(x = BMI_2step, y = mean_hb)) +
geom_bar(stat = "identity", fill = "lightsteelblue") +
labs(title = "4. 2-step grouping",
x = "BMI Group",
y = "Mean Hemoglobin (g/dL)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(hjust = 0.5))
# 5. BMI classification
p_class <- df %>%
filter(!is.na(BMI_classification)) %>%
group_by(BMI_classification) %>%
summarise(mean_hb = mean(Hemoglobin), .groups = 'drop') %>%
ggplot(aes(x = BMI_classification, y = mean_hb)) +
geom_bar(stat = "identity", fill = "lightseagreen") +
labs(title = "5. BMI classification (WHO)",
x = "BMI Classification",
y = "Mean Hemoglobin (g/dL)") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
plot.title = element_text(hjust = 0.5))
# 各プロットを個別に保存(背景白)
ggsave("scatter_plot.png", p_scatter, width = 8, height = 6, dpi = 300, bg = "white")
ggsave("tertile_plot.png", p_tertile, width = 8, height = 6, dpi = 300, bg = "white")
ggsave("quartile_plot.png", p_quartile, width = 8, height = 6, dpi = 300, bg = "white")
ggsave("quintile_plot.png", p_quintile, width = 8, height = 6, dpi = 300, bg = "white")
ggsave("2step_plot.png", p_2step, width = 8, height = 6, dpi = 300, bg = "white")
ggsave("classification_plot.png", p_class, width = 8, height = 6, dpi = 300, bg = "white")
# =============================================================================
# 分位点について(コラム用情報)
# =============================================================================
cat("\n=== 分位点(quantile)について ===\n")
cat("Tertile(三分位点):\n")
tertile_points <- quantile(df$BMI, probs = c(0, 1/3, 2/3, 1))
print(round(tertile_points, 1))
cat("\nQuartile(四分位点):\n")
quartile_points <- quantile(df$BMI, probs = c(0, 0.25, 0.5, 0.75, 1))
print(round(quartile_points, 1))
cat("\nQuintile(五分位点):\n")
quintile_points <- quantile(df$BMI, probs = c(0, 0.2, 0.4, 0.6, 0.8, 1))
print(round(quintile_points, 1))
```
---