まず、必要なパッケージを読み込む。インストールが必要なら先にインストールする。
if(!requireNamespace("foreign")) install.packages("foreign")
if(!requireNamespace("arm")) install.packages("arm")
library("foreign")
library("arm")
library("tidyverse")
## Windows ユーザは次の行をコメントアウト
theme_set(theme_gray(base_size = 10,
base_family = "HiraginoSans-W3"))
説明のために『Stataによる計量政治学』(浅野正彦, 矢内勇生. 2013)で使用されているデータ(hr96-09.dta) を使う。 まず、このデータをダウンロードし、dataディレクトリに保存する。
download.file(url = "http://yukiyanai.github.io/jp/quant-methods-stata/data/hr96-09.dta",
destfile = "data/hr96-09.dta")
Rは、他の統計分析ソフト用のデータファイルも読み込むことができる。Stata のデータファイル(拡張子が.dtaのファイル)は または foreign::read.dta()
(または haven::read_dta()
)を使って読み込める。
## Observations: 5,614
## Variables: 16
## $ year <int> 1996, 1996, 1996, 1996, 1996, 1996, 1996, 1996, 1996...
## $ ku <chr> "aichi", "aichi", "aichi", "aichi", "aichi", "aichi"...
## $ kun <int> 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3...
## $ party <fct> NFP, LDP, DPJ, JCP, bunka, NP, msz, NFP, LDP, DPJ, J...
## $ name <chr> "KAWAMURA, TAKASHI", "IMAEDA, NORIO", "SATO, TAISUKE...
## $ age <int> 47, 72, 53, 43, 51, 51, 45, 51, 71, 30, 31, 44, 61, ...
## $ status <fct> incumbent, moto, incumbent, challenger, challenger, ...
## $ nocand <int> 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 7...
## $ wl <fct> win, lose, lose, lose, lose, lose, lose, win, lose, ...
## $ rank <int> 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3...
## $ previous <int> 2, 3, 2, 0, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 0, 1, 3, 1...
## $ vote <int> 66876, 42969, 33503, 22209, 616, 566, 312, 56101, 44...
## $ voteshare <dbl> 40.0, 25.7, 20.1, 13.3, 0.4, 0.3, 0.2, 32.9, 26.4, 2...
## $ eligible <int> 346774, 346774, 346774, 346774, 346774, 346774, 3467...
## $ turnout <dbl> 49.22, 49.22, 49.22, 49.22, 49.22, 49.22, 49.22, 51....
## $ exp <int> 9828097, 9311555, 9231284, 2177203, NA, NA, NA, 1294...
衆議院議員経験があることを表すダミー変数と選挙費用を100万円単位で測定する変数を作る。
HR <- HR %>%
mutate(experience = as.numeric(status == "incumbent" | status == "moto"),
expm = exp / 10^6)
2009年の結果だけ抜き出し、HR09として保存する。
得票率(応答変数、結果変数)を議員経験(説明変数)で説明するモデルを考えよう。 議員経験は、現職または元職の候補者なら1、そうでなければ0をとる二値 (binary) 変数(ダミー変数)である。 このモデルを式で表すと、 \[得票率_i \sim \mathrm{N}(\beta_1 + \beta_2 \cdot 議員経験_i, \sigma^2)\] と、なる。
Rでは、lm()
で回帰式を推定する。
これで、fit_1
に推定結果(係数の推定値など)が保存される。
基本的な結果は、summary()
で見ることができる。
##
## Call:
## lm(formula = voteshare ~ experience, data = HR09)
##
## Residuals:
## Min 1Q Median 3Q Max
## -43.843 -12.134 -5.484 8.707 52.016
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 13.9840 0.6221 22.48 <2e-16
## experience 30.8589 0.9832 31.39 <2e-16
##
## Residual standard error: 16.26 on 1137 degrees of freedom
## Multiple R-squared: 0.4642, Adjusted R-squared: 0.4637
## F-statistic: 985 on 1 and 1137 DF, p-value: < 2.2e-16
この結果は少し読みにくいので、代わりに arm::display()
を利用しよう。
## lm(formula = voteshare ~ experience, data = HR09)
## coef.est coef.se
## (Intercept) 13.98 0.62
## experience 30.86 0.98
## ---
## n = 1139, k = 2
## residual sd = 16.26, R-Squared = 0.46
この出力の、coef.est の列に係数の推定値 (coefficient estimates) が示されている。 これにより、\(\hat{\beta}_1=\) 13.98, \(\hat{\beta}_2=\) 30.86, \(\hat{\sigma}=\) 16.26 が得られた。 したがって、 \[\widehat{得票率} = 13.98 + 30.86 \cdot 議員経験\] と、なる。
この結果を図示しよう。
p1 <- ggplot(HR09, aes(x = experience, y = voteshare)) +
scale_x_continuous(breaks = c(0,1))
p1 <- p1 + geom_jitter(position = position_jitter(width = 0.05), size = 1)
p1 <- p1 + geom_smooth(method = 'lm', se = FALSE) +
xlab('議員経験') + ylab('得票率(%)')
print(p1 + ggtitle('得票率と議員経験の関係'))
この図に推定の不確実性を示すには、geom_smooth(method = 'lm', se = TRUE)
とすればよい(が、 se = TRUE はデフォルトなので、seを指定する必要はない)。 デフォルトでは、95パーセント信頼区間が回帰直線の周りに表示される。
信頼度を変えたいとき、例えば50パーセント信頼区間を表示したいときは、次のようにlevelを指定する。
この直線の切片である13.98は、議員経験がない候補者の平均得票率(予測得票率)である。 予測値の式の「議員経験」に0を代入すれば、これは明らかである。 議員経験がある候補者の平均得票率(予測得票率)は、「議員経験」に1を代入することで得られる。 代入してみると、\(13.98 + 30.86 \cdot 1 = 44.84\) となる。
Rで議員経験ごとに平均得票率を求め、上の式から求めた予測値と一致するか確かめよう。
## # A tibble: 2 x 2
## experience voteshare
## <dbl> <dbl>
## 1 0 14.0
## 2 1 44.8
このように、予測値は予測変数の値を与えられたときの、応答変数の平均値であることがわかる。
同様に、得票率を選挙費用(測定単位:100万円)で説明するモデルは、次のように推定できる。
## lm(formula = voteshare ~ expm, data = HR09)
## coef.est coef.se
## (Intercept) 7.74 0.76
## expm 3.07 0.10
## ---
## n = 1124, k = 2
## residual sd = 16.04, R-Squared = 0.48
回帰直線を図示する。
p2 <- ggplot(HR09, aes(x = expm, y = voteshare)) + geom_point(size = 1)
p2 <- p2 + geom_smooth(method = 'lm', se = FALSE)
p2 <- p2 + labs(x = '選挙費用(100万円)', y = '得票率(%)')
print(p2 + ggtitle('得票率と選挙費用の関係'))
95パーセント信頼区間を加える。
次に、得票率を議員経験と選挙費用で説明するモデルを推定する。 lm()
で重回帰を行うときは、説明変数を + でつなぐ。
## lm(formula = voteshare ~ experience + expm, data = HR09)
## coef.est coef.se
## (Intercept) 7.91 0.69
## experience 18.10 1.23
## expm 1.85 0.12
## ---
## n = 1124, k = 3
## residual sd = 14.69, R-Squared = 0.56
この結果を図示する。
## 予測値のデータフレームを作る
pred <- with(HR09, expand.grid(
expm = seq(min(expm, na.rm = TRUE), max(expm, na.rm = TRUE), length.out = 100),
experience = c(0,1)
))
## 予測値を計算する
pred <- mutate(pred, voteshare = predict(fit_3, newdata = pred))
## 散布図 (point) は元データ (HR09) で描き、回帰直線は予測値 (pred) で描く
p3 <- ggplot(HR09, aes(x = expm, y = voteshare, color = as.factor(experience))) +
geom_point(size = 1) +
geom_line(data = pred)
p3 <- p3 + labs(x = '選挙費用(100万円)', y = '得票率(%)')
p3 <- p3 +
scale_color_discrete(name = '議員経験', labels = c('なし', 'あり')) +
guides(color = guide_legend(reverse = TRUE))
print(p3 + ggtitle('得票率と選挙費用の関係'))
この図からわかるように、このモデルは2つの直線が平行になる(傾きが同じになる)ように設定されている。
この図に95パーセント信頼区間を加える。 信頼区間を求めるために標準誤差を利用する。 また、標準誤差は\(t\) 分布に従うので、qt()
で分布の95%が収まる範囲を求める (標準正規分布で近似し、\(\pm 1.96\) を使っても結果に大きな違いはないが、せっかくRを使っているのだから、より正確な数値を利用したほうがよい)。
## 信頼区間の下限と上限を計算する
## まず、予測値と標準誤差を求める
err <- predict(fit_3, newdata = pred, se.fit = TRUE)
## 予測値と標準誤差を使って信頼区間を求める
pred$lower <- err$fit + qt(0.025, df = err$df) * err$se.fit
pred$upper <- err$fit + qt(0.975, df = err$df) * err$se.fit
p3_ci95 <- p3 +
geom_smooth(data = pred, aes(ymin = lower, ymax = upper), stat = 'identity')
print(p3_ci95 + ggtitle('得票率と選挙費用の関係'))
モデル3では回帰直線が平行になるようにモデルを制限していたが、この制限を緩めてみよう。 そのために、議員経験 \(\times\) 選挙費用という予測変数を追加する。
Rでは、lm()
内の説明変数のところに、変数A * 変数B
と書くと、変数A、変数B、変数A \(\times\) 変数B を説明変数として加えてくれるので、これを利用する。
## lm(formula = voteshare ~ experience * expm, data = HR09)
## coef.est coef.se
## (Intercept) -2.07 0.72
## experience 45.91 1.58
## expm 4.87 0.16
## experience:expm -4.76 0.21
## ---
## n = 1124, k = 4
## residual sd = 12.11, R-Squared = 0.70
この結果を図示する。
95パーセント信頼区間を加える。
回帰分析のしくみ(特に信頼区間の意味)を理解するために、簡単なモンテカルロシミュレーションを行おう。 シミュレーションでは、自分で母数を設定し、データを生成する。 そのうえで、特徴を知りたい分析手法(今回の場合はOLS)を生成したデータに当てはめ、母数をうまく推測できるかどうか確認する。
今回は、単回帰を例にシミュレーションを行う。 このシミュレーションを行う主な目的は以下の3つである。
単回帰モデルは \[y \sim \mathrm{N}(X\beta, \sigma^2 I)\] または、 \[y_i \sim \mathrm{N}(\beta_1 + \beta_2 x_i, \sigma^2)\] (\(i = 1, 2, \dots, n\))と表すことができる。 したがって、設定する母数は3つある。
次に、単回帰モデルが想定するデータ生成過程に従って、データを生成する。
n <- 100 ## サンプルサイズを決める
## x ~ U(0, 10) とする:他の設定でもかまわない
x <- runif(n, 0, 10) ## 最小値0, 最大値10の一様分布から長さnのxベクトルを抽出する
y <- rnorm(n, beta1 + beta2 * x, sd = sigma) ## 設定に従って、yを正規分布から抽出する
ここで、手に入れたデータを散布図にして直線を当てはめてみよう。
df <- data_frame(y, x)
scat <- ggplot(df, aes(x, y)) + geom_point() + geom_smooth(method = 'lm')
print(scat)
回帰式を推定すると、
## lm(formula = y ~ x, data = df)
## coef.est coef.se
## (Intercept) 9.14 1.28
## x 3.10 0.22
## ---
## n = 100, k = 2
## residual sd = 6.29, R-Squared = 0.68
3つの母数\(\beta_1=10, \beta_2=3, \sigma=6\) に対する推定値は、それぞれ9.14, 3.1, 6.29 であることがわかる。
このとき、係数の95パーセント信頼区間は、
## 2.5 % 97.5 %
## (Intercept) 6.602367 11.686254
## x 2.672670 3.526034
で求められる。 95パーセント信頼区間は、切片が[6.60, 11.69]、傾きが[2.67, 3.53] であり、どちらの信頼区間も母数を含んでいる。 つまり、ここで得られた信頼区間が母数を含む確率は1(100%)である!
以上の過程を、母数とサンプルサイズは変えずに何度も繰り返す。 同じコードを何度も繰り返し使うのは面倒なので、関数にしてしまおう。
sim_ols1 <- function(beta, sigma, n = 100, trials = 10000, x_rng = c(0,10)) {
## Function to simulate simple linear regressions
## Arguments:
## beta: vector of coefficient parameters
## sigma: standard deviation of the error
## n: sample size, default n=100
## trials: the number of simulation trials
## default trials=10000
## x_rng: range of the explanatory variable x
## default x.rng=c(0,10)
## Return:
## df: data frame containing
## (1) parameter estimates,
## (2) se of coefficient estimates, and
## (3) 95% CI of coefficient estimates
## for each trial of the simulation.
## create a data frame to save the results
col_names <- c('b1', 'b1_se', 'b1_lower', 'b1_upper',
'b2', 'b2_se', 'b2_lower', 'b2_upper', 'sigma_hat')
df <- as_data_frame(matrix(rep(NA, trials * length(col_names)),
ncol = length(col_names)))
names(df) <- col_names
for (i in 1:trials) { # loop for trials
# generate data
x <- runif(n, x_rng[1], x_rng[2])
y <- rnorm(n, mean = beta[1] + beta[2]*x, sd = sigma)
# regress y on x
fit <- lm(y ~ x)
# sum of squared residuals
sigma_hat <- summary(fit)$sigma
# coefficient estimates
b1 <- coef(fit)[1]
b2 <- coef(fit)[2]
# standard errors of coefficient estimates
b1_se <- sqrt(summary(fit)$cov.unscaled[1,1]) * sigma_hat
b2_se <- sqrt(summary(fit)$cov.unscaled[2,2]) * sigma_hat
# 95% CI
b1_ci95 <- confint(fit)[1,]
b2_ci95 <- confint(fit)[2,]
# save the results
df[i,] <- c(b1, b1_se, b1_ci95,
b2, b2_se, b2_ci95,
sigma_hat)
}
# return the data frame
return(df)
}
この関数を利用して、実際にシミュレーションを行ってみよう。 自分で母数とサンプルサイズ(ここでは規定値のn=100を利用)を指定し、データの生成と回帰式の推定を1,000回繰り返すことにする。
beta1 <- 10; beta2 <- 3; sigma <- 6
sim_1 <- sim_ols1(beta = c(beta1, beta2), sigma = sigma, trials = 1000)
head(sim_1)
## # A tibble: 6 x 9
## b1 b1_se b1_lower b1_upper b2 b2_se b2_lower b2_upper sigma_hat
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 10.5 1.33 7.87 13.2 2.84 0.228 2.39 3.29 6.30
## 2 9.20 1.56 6.10 12.3 3.16 0.243 2.68 3.64 6.56
## 3 8.68 1.37 5.97 11.4 3.17 0.231 2.71 3.63 6.61
## 4 9.69 1.42 6.87 12.5 2.91 0.235 2.45 3.38 6.31
## 5 8.81 1.11 6.60 11.0 3.04 0.202 2.64 3.44 5.70
## 6 9.21 1.09 7.06 11.4 3.38 0.189 3.00 3.75 5.66
これで、シミュレーションで得られた数字はsim_1に保存された。
説明変数 \(x\) の係数の推定値 b2 の分布を確認してみよう。 私たちは母数\(\beta_2=3\)であることを知っているが、この数値をうまく推定できるだろうか?
hist_b2 <- ggplot(sim_1, aes(x = b2, y = ..density..)) +
geom_histogram(binwidth = .1, color = 'black')
hist_b2 <- hist_b2 +
labs(y = '確率密度', title = 'シミュレーションで得たb2の分布') +
geom_vline(xintercept = 3, color = 'tomato')
print(hist_b2)
このヒストグラムが示すように、線形回帰は平均すると、母数をうまく推定してくれる(不偏性, unbiasedness)。しかし、常に正しい推定をするわけではなく、係数の値を過小推定することもあれば、過大推定することもある。実際の分析では、データセットが1つしかないのが普通であり、自分のデータ分析が係数を「正しく」推定しているとは限らならい。そのために、推定の不確実性を明示することが求められるのである。
次に、b2の標準誤差 (standar error) をヒストグラムにしてみよう。
hist_se <- ggplot(sim_1, aes(x = b2_se, y = ..density..)) +
geom_histogram(binwidth = .01, color = 'black') +
labs(x = 'b2の標準誤差', y = '確率密度', title = 'b2の標準誤差の分布')
print(hist_se)
このように、標準誤差自体が推定量なので、値はサンプルごとに異なる(分布する)。
標準誤差をseとすると、 \[\frac{\hat{\beta} - \beta}{\mathrm{se}}\] は自由度\(n-k\)(ここでは、100-2=98)の\(t\)分布に従うはずである。 まず、この値をヒストグラムにして、自由度98の\(t\)分布の確率密度曲線を重ね書きしてみよう。
sim_1 <- sim_1 %>% mutate(b2_t = (b2 - beta2) / b2_se)
## t分布の確率密度を求め、true_tという名前のデータフレームに保存する
true_t <- data.frame(x = seq(-4, 4, length = 100)) %>%
mutate(density = dt(x, df = 98))
hist_t <- ggplot(sim_1, aes(x = b2_t, y = ..density..)) +
geom_histogram(binwidth = 0.5, color = 'black') +
geom_line(data = true_t, aes(x = x, y = density), color = 'tomato')
hist_t <- hist_t +
labs(x = expression(frac(hat(beta) - beta, 'se')),
y = '確率密度', title = '標準化されたb2と自由度98のt分布')
print(hist_t)
この図から、\(t\)分布に近い分布であることがわかる。 Q-Qプロットでも確かめてみよう。
qqplot_t <- ggplot(sim_1, aes(sample = b2_t)) +
stat_qq(distribution = qt, dparams = list(df = 98)) +
geom_abline(intercept = 0, slope = 1, color = 'tomato') +
labs(title = 'Q-Q プロット',
x = '自由度n-k=98のt分布',
y = expression(paste('シミュレートした ',
frac(hat(beta) - beta, 'se'),
' の分布')))
print(qqplot_t)
Q-Qプロットの点がほぼ一直線に並んでおり、\((\hat{\beta}-\beta)/\mathrm{se}\)が\(t\)分布に従っていることがわかる。ただし、分布の裾では、理論値との乖離が大きいことに注意が必要である。
今回のシミュレーションで得られた標準誤差の平均値は、
## [1] 0.209381
である。標準誤差は推定値の標準偏差のはずだが、本当にそうなっているかどうか確認してみよう。
## [1] 0.2144247
これで、実際に標準誤差は推定値の標準偏差に(ほぼ)一致することがわかった。
係数の推定値b2の95パーセント信頼区間を例として考える。 シミュレーションで得られた1つ目の信頼区間は、
## # A tibble: 1 x 2
## b2_lower b2_upper
## <dbl> <dbl>
## 1 2.39 3.29
すなわち、[2.39, 3.29] がb2[1] の95パーセント信頼区間である。 この区間は母数である\(\beta_2=3\)を区間内に含んでいる。 したがって、この信頼区間が母数を含む確率は1(100%)である。
同様に、2つ目の信頼区間は、
## # A tibble: 1 x 2
## b2_lower b2_upper
## <dbl> <dbl>
## 1 2.68 3.64
であり、これも母数を区間内に含んでいる。
しかし、39番目の信頼区間は、
## # A tibble: 1 x 2
## b2_lower b2_upper
## <dbl> <dbl>
## 1 3.03 3.75
であり、これは母数を区間内に含んでいない。 つまり、この信頼区間が母数を含む確率は0である。
シミュレーションで得た1,000個の信頼区間のうち、どの信頼区間が母数を含んでいるか調べてみよう。 母数を信頼区間内に含むのは、信頼区間の下限値が母数以下かつ上限値が母数以上のものである。
この結果、TRUEとなっているものが母数を区間内に捉えているもの、FALSEがそうでないものである。これを表にすると、
## check_ci
## FALSE TRUE
## 49 951
と、なる。つまり、1000個の95パーセント信頼区間のうち、951個(95.1%)は母数を区間内に捉え、残りの49個が捉えないということである。このように、一定のデータ生成過程から得られた異なるデートセットに対し、信頼区間を求める作業を繰り返したとき、「得られた信頼区間のうち何パーセントが母数を区間内に含むか」というのが、信頼区間の信頼度である。
これを図にしてみよう。 1000個は多すぎるので、無作為に100個だけ選んで図示する。
# Give an ID to each simulated result
sim_1 <- sim_1 %>% mutate(id = 1:n())
# Put check_ci into the data frame
sim_1$check_ci <- check_ci
# Randomly choose 100 rows of sim values for graph
sim_1_sml <- sim_1 %>% sample_n(100)
# Show simulated ci's graphically
ctplr <- ggplot(sim_1_sml,
aes(x = reorder(id, b2), y = b2,
ymin = b2_lower, ymax = b2_upper,
color = check_ci)) +
geom_hline(yintercept = beta2, linetype = 'dashed') +
geom_pointrange() +
labs(x = 'シミュレーションID', y = 'b2', title = '95%信頼区間') +
scale_color_discrete(name = '母数を', labels = c('含まない', '含む')) +
coord_flip()
print(ctplr)
このように、100個中95個(95パーセント)の95パーセント信頼区間が、母数である3にかかっていることがわかる。