Rの関数:difference_in_means {estimatr}

Rの関数から difference_in_means {estimatr} を確認します。

本ポストはこちらの続きです。

Rの関数:commarobust {estimatr}
const typesetMath = (el) => { if (window.MathJax) { // MathJax Typeset window.MathJax.typeset(); } else if (window.katex...

関数 difference_in_means とは

difference_in_means は、2つのグループ間(処置群と対照群など)の平均値の差(Average Treatment Effect: ATE)を推定するための関数です。

Rの標準的な t.test 関数と似ていますが、difference_in_meansデザインベースの推論(Design-Based Inference)、特にネイマン(Neyman)の枠組みに基づいている点が異なります。

実験デザインにおける「クラスター化(Clustering)」、「ブロック化(Blocking / Stratification)」、「重み付け(Weighting)」、「ペアマッチング」を明示的に考慮し、不均一分散(Heteroskedasticity)に対して頑健な標準誤差を計算します。

関数 difference_in_means の引数

library(estimatr)
args(difference_in_means)
function (formula, data, blocks, clusters, weights, subset, se_type = c("default", 
    "none"), condition1 = NULL, condition2 = NULL, ci = TRUE, 
    alpha = 0.05) 
NULL
  1. formula

    • 解析モデルを指定する式です。形式は outcome ~ treatment (被説明変数 ~ 処置変数)となります。
    • 右辺(説明変数)は1つの変数のみである必要があります。重回帰分析を行う関数ではないためです。
    • 比較したい結果と、グループ分けの変数を定義します。
  2. data

    • formula で指定された変数を含むデータフレームです。
    • 解析対象のデータセットを渡します。
  3. blocks

    • ブロック(層化)変数を指定します。
    • 実験がブロック化無作為化(層化無作為化)で行われた場合、当該変数を指定することで、ブロックごとの変動を除去し、推定精度を向上させます。ブロック内で推定値を計算し、それをサンプルサイズで加重平均して全体の効果を算出します。
  4. clusters

    • クラスター変数を指定します。
    • 実験がクラスター無作為化(例:個人ではなく学校や村単位での割り付け)で行われた場合、観測値間の相関を考慮して標準誤差(クラスターロバスト標準誤差)を計算します。
  5. weights

    • 重み付け変数を指定します。
    • 調査のサンプリングウェイトや、逆確率重み付け(IPW)などを用いる場合に指定します。
  6. subset

    • 分析に使用するデータのサブセット条件を指定します。
    • データをフィルタリングして分析する場合に使用します(例:女性のみ、特定の地域のみなど)。
  7. se_type

    • 標準誤差の計算方法を指定する文字列です。
    • デフォルト: "default"(通常、不均一分散を考慮した HC2 や、クラスターがある場合は CR2 が自動選択されます)。
    • 選択肢: "none", "HC0", "HC1", "HC2", "HC3", "stata", "CR0", "CR2" など。
    • 分析の前提に応じた分散推定法を選択します。
  8. condition1, condition2

    • 比較する2つの条件(水準)を明示的に指定します。
    • デフォルト: NULL(自動的にデータから2つの値を検出します)。
    • 処置変数が3つ以上の水準を持つ場合や、基準となる群(コントロール群)を明確に指定したい場合に使用します。通常、condition1 がコントロール(参照群)、condition2 がトリートメント(処置群)となります。
  9. ci

    • 信頼区間を計算するかどうかの論理値です。
    • デフォルト: TRUE
  10. alpha

    • 有意水準を指定する数値です。
    • デフォルト: 0.05(95%信頼区間)。

シミュレーションコード

以下に、difference_in_means の機能を確認するためのサンプルデータを用いたシミュレーションコードを示します。

このシミュレーションでは、「新しい教育プログラムの効果測定」というシナリオを設定します。

実験デザインとして、単なるランダム化ではなく、「ブロック化(地域)」「クラスター化(クラス単位)」の双方が行われたケースを作成します。

このデータに対し、通常のt検定を行った場合と、difference_in_means でデザインを考慮した場合の違いを比較します。

なお、有意水準は5%とします。

シミュレーションデータの生成

# パッケージの読み込み
library(ggplot2)
library(dplyr)

# 乱数シードの固定
seed <- 20251212
set.seed(seed)

# 設定: 教育プログラムの実験
# 地域(ブロック): 都市部 (Urban) と 地方部 (Rural)
# クラスター: 学校のクラス単位でプログラムを導入するか決定
# 個人: 生徒

# パラメータ設定
n_blocks <- 2 # 都市部、地方部
n_clusters_per_block <- 20 # 各地域に20クラスずつ
n_students_per_cluster <- 15 # 1クラス15人
Total_N <- n_blocks * n_clusters_per_block * n_students_per_cluster

# データの骨組み作成
data_sim <- data.frame(
  block_id = rep(c("都市部", "地方部"), each = n_clusters_per_block * n_students_per_cluster),
  cluster_id = rep(1:(n_blocks * n_clusters_per_block), each = n_students_per_cluster)
)

# 処置の割り当て
cluster_treatment <- data_sim %>%
  group_by(block_id, cluster_id) %>%
  summarise(temp = 1, .groups = "drop") %>%
  group_by(block_id) %>%
  mutate(treatment = sample(rep(c(0, 1), length.out = n()))) %>%
  ungroup() %>%
  select(cluster_id, treatment)

# データを結合
data_sim <- left_join(data_sim, cluster_treatment, by = "cluster_id")

# 変数の生成
base_score_block <- ifelse(data_sim$block_id == "都市部", 60, 50)
cluster_noise_vals <- rnorm(n_blocks * n_clusters_per_block, mean = 0, sd = 5)
data_sim$cluster_noise <- cluster_noise_vals[data_sim$cluster_id]
individual_noise <- rnorm(Total_N, mean = 0, sd = 8)
true_effect <- 5

data_sim$score <- base_score_block +
  (true_effect * data_sim$treatment) +
  data_sim$cluster_noise +
  individual_noise

data_sim$treatment_label <- ifelse(data_sim$treatment == 1, "実施あり", "実施なし")

cat("--- データの一部を確認 ---\n")
print(str(data_sim))

cat("\n--- データ概要 ---\n")
cat(sprintf("総観測数: %d 人\n", nrow(data_sim)))
cat(sprintf("ブロック数: %d (都市部, 地方部)\n", length(unique(data_sim$block_id))))
cat(sprintf("クラスター数: %d (クラス単位)\n", length(unique(data_sim$cluster_id))))
cat(sprintf("真の処置効果: %+.1f 点", true_effect))

# データの可視化
p1 <- ggplot(data_sim, aes(x = treatment_label, y = score, fill = treatment_label)) +
  geom_boxplot(alpha = 0.6) +
  facet_wrap(~block_id) +
  labs(
    title = "地域(ブロック)ごとのテストスコア分布",
    subtitle = "都市部の方が全体的に点数が高い(ブロック差)。処置群の方が点数が高い傾向。",
    x = "プログラム実施状況",
    y = "テストスコア"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

print(p1)
--- データの一部を確認 ---
'data.frame':   600 obs. of  6 variables:
 $ block_id       : chr  "都市部" "都市部" "都市部" "都市部" ...
 $ cluster_id     : int  1 1 1 1 1 1 1 1 1 1 ...
 $ treatment      : num  1 1 1 1 1 1 1 1 1 1 ...
 $ cluster_noise  : num  1.92 1.92 1.92 1.92 1.92 ...
 $ score          : num  77.6 71.9 74.1 63.1 76 ...
 $ treatment_label: chr  "実施あり" "実施あり" "実施あり" "実施あり" ...
NULL

--- データ概要 ---
総観測数: 600 人
ブロック数: 2 (都市部, 地方部)
クラスター数: 40 (クラス単位)
真の処置効果: +5.0 点
Figure 1

分析1: 通常のt検定(デザイン無視)

t_test_res <- t.test(score ~ treatment, data = data_sim, var.equal = FALSE)

# 結果の表示
print(t_test_res)

cat("------------------------------------------------------------\n")

cat("推定された差 (平均): ", diff(t_test_res$estimate), "\n")
cat("p値: ", format.pval(t_test_res$p.value), "\n")
cat("95%信頼区間: [", t_test_res$conf.int[2] * -1, ", ", t_test_res$conf.int[1] * -1, "]\n")

    Welch Two Sample t-test

data:  score by treatment
t = -7.3634, df = 597.95, p-value = 5.97e-13
alternative hypothesis: true difference in means between group 0 and group 1 is not equal to 0
95 percent confidence interval:
 -7.620678 -4.411482
sample estimates:
mean in group 0 mean in group 1 
       54.12970        60.14578 

------------------------------------------------------------
推定された差 (平均):  6.01608 
p値:  5.9697e-13 
95%信頼区間: [ 4.411482 ,  7.620678 ]

分析2: difference_in_means(デザイン考慮)

dim_res <- difference_in_means(
  formula = score ~ treatment,
  data = data_sim,
  blocks = block_id, # 地域による層化
  clusters = cluster_id # クラス単位の相関
)

# 結果の表示
print(dim_res)

cat("------------------------------------------------------------\n")

# 結果の抽出
coef_val <- dim_res$coefficients
se_val <- dim_res$std.error
ci_lower <- dim_res$conf.low
ci_upper <- dim_res$conf.high
design_info <- dim_res$design

cat(sprintf("デザイン: %s\n", design_info))
cat(sprintf("推定値 (Estimate): %f\n", coef_val))
cat(sprintf("標準誤差 (Std. Error): %f\n", se_val))
cat(sprintf("95%%信頼区間: [%f, %f]\n", ci_lower, ci_upper))
Design:  Block-clustered 
          Estimate Std. Error  t value     Pr(>|t|) CI Lower CI Upper DF
treatment  6.01608   1.515765 3.969007 0.0003299954 2.941967 9.090193 36
------------------------------------------------------------
デザイン: Block-clustered
推定値 (Estimate): 6.016080
標準誤差 (Std. Error): 1.515765
95%信頼区間: [2.941967, 9.090193]

結果の比較

推定値(Estimate)の一致

両者の処置効果の推定値(Estimate)は以下の通り、一致しています。

  • 通常のt検定: 平均の差は約 6.01608
  • difference_in_means: 推定値は 6.01608

これは、両手法ともに「処置群の平均 - 対照群の平均」という基本的な計算を行っているためです。

標準誤差(Std. Error)と自由度(DF)の乖離
  • 通常のt検定:

    • 標準誤差: 明示されていませんが、\(t = \text{Est} / \text{SE}\) の関係から逆算すると、\(\approx 0.817\) となります。
    • 自由度 (df): 597.95
    • 解釈: 「600人の生徒全員が独立した情報源である」と仮定しているため、標準誤差を、difference_in_meansと比較して、小さく見積もり、自由度をサンプルサイズ数(約600)として計算しています。
  • difference_in_means:

    • 標準誤差: 1.5158 (t検定の約1.85倍)
    • 自由度 (DF): 36
    • 解釈: 「情報は生徒数(600)ではなく、クラスター数(40クラス)に依存する」と認識しています。同じクラスの生徒は似た傾向を持つ(相関がある)ため、有効な情報量は生徒数よりもずっと少なくなります。それゆえ、標準誤差は補正され、自由度はクラスター数に基づいた 36 まで減少しました。
信頼区間(CI)とp値の更新

標準誤差と自由度の修正は、信頼区間とp値に影響を与えます。

  • 信頼区間:

    • 通常のt検定: [4.411, 7.621] (幅: 約3.2)
    • difference_in_means: [2.942, 9.090] (幅: 約6.1)
  • p値:

    • 通常のt検定: 5.97e-13
    • difference_in_means: 0.00033
    • 今回のシミュレーションでは、どちらも設定した有意水準を下回っていますが、もし真の効果がもっと小さく、例えば 通常のt検定 のp値が 0.049と有意水準未満、difference_in_meansのp値が0.085と有意水準を超えるような場合、difference_in_means では「帰無仮説は棄却できない」と、正しい結論に収まりますが、t検定では「有意」と誤認されてしまいます。

t検定における標準誤差の産出

方法1: t.testの結果オブジェクトから抽出
# t.testの戻り値には "stderr" という要素が含まれています
se_extracted <- t_test_res$stderr

cat("方法1:t.testオブジェクトから抽出:\n")
cat(sprintf("標準誤差: %.10f\n\n", se_extracted))
方法1:t.testオブジェクトから抽出:
標準誤差: 0.8170302847
方法2: 生データからWelchの公式で手計算
# 各群のデータを抽出
group_0 <- subset(data_sim, treatment == 0)$score
group_1 <- subset(data_sim, treatment == 1)$score

# 各群の統計量を計算
n0 <- length(group_0) # サンプルサイズ
v0 <- var(group_0) # 不偏分散
n1 <- length(group_1)
v1 <- var(group_1)

# Welchのt検定における標準誤差の公式
# SE = sqrt( (s1^2 / n1) + (s2^2 / n2) )
se_calculated <- sqrt((v0 / n0) + (v1 / n1))

cat("方法2:公式に基づく手計算:\n")
cat(sprintf("群0 (n=%d, var=%.3f)\n", n0, v0))
cat(sprintf("群1 (n=%d, var=%.3f)\n", n1, v1))
cat(sprintf("計算された標準誤差: %.10f\n\n", se_calculated))
方法2:公式に基づく手計算:
群0 (n=300, var=101.054)
群1 (n=300, var=99.208)
計算された標準誤差: 0.8170302847

以上です。