## はじめに データ分析では、連続変数を群に分けて解析することが頻繁にあります。群分けによって、より理解しやすい形で結果を示したり、群間比較を行ったりすることができます。 BMI(Body Mass Index)を例として、連続変数を群分けする5つの異なる方法を実例とともに紹介し、それぞれの特徴について解説します。 ## サンプルデータについて BMIとヘモグロビン濃度の関係を例に、以下の関係性を持つサンプルデータを作成しました: ![BMIとヘモグロビンの散布図](scatter_plot.png) 上図は、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) - 統計学的検出力が均等 - カットポイントがデータ依存的 ![Tertile分類によるヘモグロビン平均値](tertile_plot.png) ### 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) ``` ![Quartile分類によるヘモグロビン平均値](quartile_plot.png) ### 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) ``` 各群のサンプルサイズが小さくなります。 ![Quintile分類によるヘモグロビン平均値](quintile_plot.png) ### 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) ``` **特徴:** - カットポイントが客観的で予め決定 - 群間での人数の違いが大きい場合がある - 臨床的に意味のある間隔を設定可能 ![2kg/m²間隔分類によるヘモグロビン平均値](2step_plot.png) ### 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) ``` **特徴:** - 国際的に標準化された分類 - 臨床的意義が明確 - 他研究との比較が容易 - 群間で人数の差が生じる場合がある ![WHO基準によるBMI分類とヘモグロビン平均値](classification_plot.png) ## 結果の比較 各分類方法による結果を比較すると、以下のことが分かります: 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)) ``` ---