餡子付゛録゛

ソフトウェア開発ツールの便利な使い方を紹介。

Rでガブリエル比較区間を計算する方法

データを男女などで二つ以上の群にわけて、比較したいときは色々とあります。平均や分散の違いをt検定やF検定などで比較するのが教科書的かつ堅実な方法ですが、統計学に必ずしも詳しくない人々に説明する場合は、ビジュアライゼーションも考えていかないといけません。

この目的でよく使われるのは信頼区間によるエラーバーがついたグラフですが、実は往々にして誤解されています*1。2つの群のエラー・バーは重なっているのに、t検定で統計的に有意な差がでるときがあり、群を複数に分割したときの多重比較のための補正も行われません。比較対象が群ではなくて0などある一点であれば、信頼区間の厳密な説明*2とはちょっと違う使い方ではあるものの、t検定と合致した結果が得られるわけですが。

こういうわけで、群と群をグラフで比較するのには、信頼区間は使わない方が望ましいです。ガブリエル比較区間Gabriel (1978))と言う手法があるので、こちらを使うことをお勧めします。多重比較のための補正も行ってくれます。rgabrielと言うパッケージがあるので、使ってみましょう。

# 信頼区間のエラーバーが重なり、ガブリエル比較区間ではそうでないデータセット
df1 <- data.frame(category = c("C","C","C","C","C","C","T-1","T-1","T-1","T-1","T-1","T-1"), value = c(0.117,-0.718,0.7199,-0.2422,-0.3391,-0.2223,1.0094,1.4306,0.099,1.2839,0.403,0.5616))

# 有意水準(片側)
alpha <- 0.025

# ライブラリを呼び出す.インストールしていない場合は install.packages("rgabriel")を
library(rgabriel)

# 信頼区間とガブリエル比較区間を計算
attach(df1)
df2 <- data.frame(
  category=levels(category),
  count = as.numeric(tapply(value, category, length)),
  mean = as.numeric(tapply(value, category, mean)),
  sd = as.numeric(tapply(value, category, sd))
)
df2$se <- df2$sd/sqrt(df2$count-1)
df2$t_upper <- df2$mean + df2$se*qt(1 - alpha, df=df2$count-1)
df2$t_lower <- df2$mean - df2$se*qt(1 - alpha, df=df2$count-1)
df2$g_upper <- df2$mean + rgabriel(df1$value, df1$category, alpha)
df2$g_lower <- df2$mean - rgabriel(df1$value, df1$category, alpha)
detach(df1)

データフレームdf2の中のt_upperとt_lowerが信頼区間、g_upperとg_lowerがガブリエル比較区間になります。信頼区間では群Cのt_upperは群T-1のt_lowerよりも大きいですが、g_upperはg_lowerよりも小さくなっています。グラフを描くと、以下のようにガブリエル比較区間の方が縮まっているのが分かります。

f:id:uncorrelated:20190409102836p:plain
ガブリエル比較区間と信頼区間

t検定をかけて、どちらが正しいかを見てみましょう。

t.test(df1$value[df1$category=="C"], df1$value[df1$category=="T-1"])

両側検定で5%有意で帰無仮説が棄却され、群Cと群T-1に差があることが分かります。なお、多重比較のための補正は手法によって結果が変わるので、検定結果と整合的になるとは限らないことには注意してください。

グラフのソースコード

gabriel.plotがあるので、plotすればグラフになるのですがビジュアル的にイマイチだったので、以下のように描画しました。

windowsFonts(Meiryo = windowsFont("Meiryo UI"))
par(family="Meiryo", mar=c(2, 1, 1, 1), mgp=c(2, 1, 0))

unit <- 0.1
margin <- 0.05 # 信頼区間のエラーバーとガブリエル比較区間のエラーバーの隙間の幅
ylim <- c(unit*floor(min(df2$t_lower, df2$g_lower)/unit), unit*ceiling(max(df2$t_upper, df2$g_upper)/unit))
xlim <- c(c(0.5, length(df2$mean) + 0.5))
plot((1:length(df2$mean)) - margin/2, df2$mean, type="p", ylim=ylim, xlim=xlim, axes=FALSE, xlab="", ylab="", pch=19, col="red")
arrows(1:length(df2$mean) - margin/2, df2$g_lower, 1:length(df2$mean) - margin/2, df2$g_upper, code=0, col="red", lwd=2)
arrows(1:length(df2$mean) + margin, df2$t_lower, 1:length(df2$mean) + margin, df2$t_upper, code=0, lwd=2, lty=2)
points(1:length(df2$mean) + margin, df2$mean, pch=19)

abline(h=df2$t_upper[1], lty=2)
abline(h=df2$g_upper[1], col="red", lty=3)

abline(h=df2$t_lower[1], lty=2)
abline(h=df2$g_lower[1], col="red", lty=3)

axis(1, at=c(xlim[1], 1:length(df2$mean), xlim[2]), labels=c("",as.character(df2$category),""), pos=ylim[1], las=2)

legend("topleft", c(sprintf("%.0f%%信頼区間", (1-2*alpha)*100), "ガブリエル比較区間"), col=c("black", "red"), lty=c(2, 1), lwd=c(2, 2), bg='white', box.col = 'black', bty="n")

メタアナリシス

rgabriel関数のソースコードを見ていると、群を表すファクターごとの標本標準偏差のベクトル(s)と、ファクターごとのサブサンプル・サイズのベクトル(n)と、有意水準(a)があればガブリエル比較区間は計算できるので、複数の分析のデータをつなぐことも簡単にできると思います。

*1:ダメな統計学」第6章

*2: 真の平均値が観測値だとしたときに所定の観測数のデータをとると、観測される平均値が信頼区間に収まる確率が…と言うようなものです。

クロス集計表のセルごとの有意確率を出してみよう

他人への質問を眺めていても学ぶことはあります。昨日は「クロス集計の独立性検定を示した表で、セルごとにアスタリスクがあるのは何故か?」と言う質問がされていましたが、私も理由が思いつきませんでした。以下のような頻度表、時折、見てきた気がするのですが、どうやって個々のセルの有意性を出しているのでしょうか?

JPN USA CHN
MEAT 10 13 6
FISH 9 18** 3**
BEAN 20 15** 31**

Note) ** and * are statistically significant at the 1% and 5% level, respectively.

独立性検定(全体)

まずはχ二乗検定をしてみましょう*1
頻度が入った行列mを作ってchisq.test(m)で終わるのですが、今回は教科書的にチマチマと計算してみます。ただし、1%、5%棄却粋のχ二乗値を出して比較することはせずに、P値は計算してもらいます。

# 頻度の行列をつくる
m <- matrix(c(10,9,20,13,18,15,6,3,31), 3)

# 見やすいように行名と列名をつけておく
colnames(m) <- c("JPN", "USA", "CHN")
rownames(m) <- c("MEAT", "FISH", "BEAN")

# 列ごとの合計
apply(m, 2, sum)

# 行ごとの合計
apply(m, 1, sum)

# 全体の合計
sum(m)

# 自由度を計算する
degf <- (ncol(m) - 1)*(nrow(m) - 1)

# 行(MEAT, FISH, BEAN)ごとにの期待確率
exp_p <- apply(m, 1, sum)/sum(m)

# 列(JPN, USA, CHN)ごとに期待確率exp_pをかけて、期待値の行列をつくる
exp_m <- sapply(apply(m, 2, sum), function(x){
  x * exp_p
})

# 観測値の行列と期待値の行列のセルごとの差分の二乗を合計して期待値で割り、χ二乗値を求める
chisq_v <- sum((m - exp_m)^2/exp_m)

# P値を求める
pchisq(chisq_v, degf, lower.tail=FALSE)

当然ですが、0.0008233372と、一つしかP値は出ません。

セルごとに検定

残差の二乗値を期待値で割ったものの合計がχ二乗分布に従うことから検定をかけたわけですが、今度は個々のセルの調整済み*2残差が正規分布に従うことから検定をかけます。
これも2*pnorm(abs(chisq.test(m)$stdres), lower.tail=FALSE)で終わるのですが、チマチマとやっていきましょう。

# (1-列合計/合計)(1-行合計/合計)の修正用係数の行列
adj_m <- matrix(rep(1-apply(m, 2, sum)/sum(m), ncol(m)) * rep(1-apply(m, 1, sum)/sum(m), each=nrow(m)), nrow(m), byrow=TRUE)

# 標準化残差
stdres <- (m - exp_m)/sqrt(c(adj_m) * c(exp_m))

# セルごとのP値をまとめた行列をつくる
m_p_value <- 2*pnorm(abs(stdres), lower.tail=FALSE)

今回の結果は以下のようになりました。

JPN USA CHN
MEAT 0.6633 0.3064 0.1362
FISH 0.8707 0.0025 0.0030
BEAN 0.8189 0.0006 0.0001

多重比較のためのP値の調整

期待値1%の事象でも、100回繰り返すと64%の確率で1度は生じることになります。この理屈でクロス集計表が細かく切られている場合、偽陽性が出やすくなります。多重比較のために、P値を調整しましょう。ここはライブラリにお任せです。

m_adj_p_value <- matrix(p.adjust(m_p_value), nrow(m_p_value))
dimnames(m_adj_p_value) <- dimnames(m) # 見やすいように行名と列名をコピーしておく

p.adjustでとれる調整法は多彩ですが、デフォルトはHolm法になっています。

JPN USA CHN
MEAT 1 1.0000 0.6812
FISH 1 0.0176 0.0183
BEAN 1 0.0045 0.0013

今回は解釈を左右するような変化はないですが、P値が大きくなっていることに注意してください。FISH行の1%有意が5%有意になりました。なお、この手のP値の調整は偽陰性が大きくなる傾向がある*3そうです。

参考文献

本稿の作成において「クロス集計表の有意差検定 | 象と散歩」と「Rでクロス集計表の残差分析 - bob3’s blog」を参考にしました。

*1: サンプルサイズが小さく、セルの度数が一つでも5未満の場合は、多項分布を前提にしたFisherの正確検定を使うべきと言う話もありますが、ここでは無視します。

*2: 自由度を調整する作業になると思いますが、Agresti (2018) などで確認してください。Rのヘルプの雰囲気ではたぶん、理由が分かるはずです。たぶん(´・ω・`)ショボーン

*3: Bonferroni法、Holm法、False Discovery Rate | 大阪大学腎臓内科

行列を使った計算で、SURの感じを掴んでみよう

計量の教科書には載っているものの非線形モデルには使えず、内生性を制御できないので顧みられることの少ないSUR *1 をふと思い出したので、手順を確認してみました。

1. 2つの方程式からなるSURモデル

理屈はGreeneのEconometric Analysisに詳しく載っているので参照して欲しいのですが、SURの概要を説明します。
以下の連立方程式を同時推定することを考えます*2

{\displaystyle 
\begin{eqnarray}
  \left\{
    \begin{array}{l}
		y_{1} = X_{1}\beta_1 + \epsilon_{1} \\
		y_{2} = X_{2}\beta_2 + \epsilon_{2}
    \end{array}
  \right.
\end{eqnarray}
}

 yは被説明変数のベクトル、 Xは説明変数の行列、 \betaは係数、 \epsilonは誤差項のベクトルです。添字の数字は方程式を表します。
一つ一つ推定しても良い気がしますが、 \mbox{COV}(\epsilon_1, \epsilon_2) \neq 0の場合は推定量を改善する余地があります。

例えば、天候データが無いときに2つの品種の収穫高と肥料の関係を推定するとして、日照量や気温など2つの方程式に同時に同じような影響を与える要因がある場合、 \mbox{COV}(\epsilon_1, \epsilon_2)という情報も使う方が推定結果は真の値に近づきます。

さて、最初は \mbox{COV}(\epsilon_1, \epsilon_2)の情報が無いので、以下をOLSで推定します。

{\displaystyle 
	\begin{pmatrix}
		y_{1} \\
		y_{2}
	\end{pmatrix}
	=
	\begin{pmatrix}
		X_{1} & 0 \\
		0 & X_{2}
	\end{pmatrix}
	\begin{pmatrix}
		\beta_{1} \\
		\beta_{2}
	\end{pmatrix}
	+
	\begin{pmatrix}
		\epsilon_1 \\
		\epsilon_2
	\end{pmatrix}
}

これで、以下の分散共分散行列 \Sigmaがつくれるようになりました。

{\displaystyle 
	\Sigma= 
	\begin{pmatrix}
		\mbox{COV}(\epsilon_1, \epsilon_1) & \mbox{COV}(\epsilon_1, \epsilon_2) \\
		\mbox{COV}(\epsilon_1, \epsilon_2) & \mbox{COV}(\epsilon_2, \epsilon_2)
	\end{pmatrix}
}

これをウェイトに使って、不均一分散の補正をした回帰(FGLS)をかけます。
つまり、以下のように表記を簡素化して、

{\displaystyle 
	y = X\beta + \epsilon
}

以下のようにGLS推定量を求めます。


\newcommand{\argmin}{\mathop{\rm arg~min}\limits}
{\displaystyle 
	\hat{\beta} = \argmin_{\beta} \space  (y - X \beta)^t ( \Sigma^{-1} \otimes I_n ) (y - X \beta)
}

ここで、 \otimesクロネッカー積、 I_nはn行n列の対角行列、 \Sigma \otimes I_nは共分散を対角成分にとる細胞で構成される行列です。 nは観測数です。後述するコマンドを見れば、何が起きるかは一目瞭然だと思います。

この式を満たすために、 \beta微分して一階条件を出して整理すると、

{\displaystyle 
	\hat{\beta} = (X^t ( \Sigma^{-1} \otimes I_n ) X)^{-1} X^t ( \Sigma^{-1} \otimes I_n ) y
}

となります。 (\Sigma \otimes I_n)^{-1} = \Sigma^{-1} \otimes I_nに注意してください。

2. RによるSUR推定

systemfitパッケージを使えば良いのですが、前節で説明したモデルとの対応関係が分からなくなるので、行列を使って計算します。

#
# データセットの作成
#
set.seed(20181103)
n <- 50 # 標本サイズが小さい方が、OLSとSURの差が出る

l <- rnorm(n, sd=2) # 共分散を持つようにする
e1 <- rnorm(n, sd=1) + l
e2 <- rnorm(n, sd=1) + l

x1 <- runif(n, min=0, max=2)
x2 <- runif(n, min=-2, max=2)
y1 <- 1 + 2*x1 + e1
y2 <- 3 + 2*x2 + e2

X1 <- matrix(c(rep(1, n), x1, rep(0, 2*n)), n, 4)
X2 <- matrix(c(rep(0, 2*n), rep(1, n), x2), n, 4)
X <- rbind(X1, X2)
y <- c(y1, y2)

#
# 以下、実際のSUR推定量の計算
#
# 1段階目のOLS推定量を計算
beta_ols <- solve(t(X)%*%X)%*%t(X)%*%y

# 誤差項を計算
r_e <- y - X %*% beta_ols
r_e1 <- r_e[1:n]
r_e2 <- r_e[(n+1):(2*n)]

# ∑を計算
sigma <- matrix(c(cov(r_e1,r_e1), cov(r_e1,r_e2), cov(r_e1,r_e2), cov(r_e2,r_e2)), 2, 2)

# SUR推定量を計算
beta_sur <- solve(t(X) %*% (solve(sigma) %x% diag(n)) %*% X) %*% t(X) %*% (solve(sigma) %x% diag(n)) %*% y

なお、beta_ols - beta_surで推定量にどれぐらい違いがあるか分かるので、nの値を変えて試してみてください。t値やP値はOLSと同様に計算できます。また、sigma %x% diag(n)とすると、 n  \times nの巨大行列ですが、 \Sigma \otimes I_nがどうなっているか分かります。

*1: すぐに同時性ガガガ…と言われるので、GMMか3SLS、もしくは全情報最尤法を使うのが一般的で、SUR自体は3SLSを学ぶ前の予備知識的な扱いが多いと思います。

*2:もっとも単純なモデルを考えましたが、二つの式で、係数が同じ値を持つような制約を置くこともできます。

相関行列から一定以上の大きさの変数の組み合わせを抽出

100や1000もある説明変数同士の相関係数を見るはだるいと言う話があって、確かに100×100や1000×1000どころか、10×10の行列でも目視で確認すると見落としが出そうです。相関係数0.5以上をリストするコードを書いてみましょう。

# テスト用のデータフレームを作る
set.seed(20180925)

n <- 100 # 変数の長さ
m <- 100 # 変数の種類
df1 <- data.frame(x001 = rnorm(n))
for(i in 2:m){
  vn <- sprintf("x%03d", i)
  df1[vn] <- rnorm(n)
  # 以下、ランダムに、既に生成済みの変数からの影響を加える
  xi <- as.integer(runif(1, max=i))
  if(0<xi){
    xn <- sprintf("x%03d", xi)
    beta <- runif(1, min=-1, max=1)
    df1[vn] <- df1[vn] + beta*df1[xn]
  }
}

# だるさを確認
cor(df1)

# 機械的相関係数が0.5より大なものを選ぶ
t <- 0.5
n <- names(df1)
l <- length(n)
s <- 1:l^2-1
flag <- c(abs(cor(df1))>t) # 相関係数がtより大なものはTRUE
flag[seq(1, l^2, l + 1)] <- FALSE # 自己相関は排除

# 結果表示
pair <- sprintf("cor(%s,%s)>%.1f", n[floor(s/l) + 1], n[s %% l + 1], t)
pair[flag]

9900の組み合わせを目視するよりはましですが、116個も出てくるとどれを落とそうか迷いそうですね。こういう状態で回帰分析をするときは、L1/L2正則化項を加えるか、主成分分析などで変数をまとめた方が良さそうです。やはり見る意味はないかも知れません。

よくある操作変数法(IV)のための練習データとOLSによる推定バイアス

内生性について上手くツイートでは問題点を説明できないので、念頭に置いているコードを出します。

#
# 教科書例の需要と供給のデータを作る
# 真のモデル:
# S = 2 + 3*p + 4*z + ν
# D = 1 - 1*p + μ
# S = D
# p:価格, S:供給, D:需要, z:気候か何か, νとμ:誤差項
# → 価格について式を整理してみると、D=Sと言う式から、P = (2 - 1)/(-1 - 3) + 4・z/(-1 - 3) + (ν - μ)/(-1 - 3)になって、Pに推定する需要関数の誤差項μが含まれていることになり、OLSの条件を満たしません。
#
set.seed(20180411)
obs <- 300 # 観測数(増やしてバイアスの変化をチェック!)
a0 <- 1
a1 <- -1
b0 <- 2
b1 <- 3
b2 <- 4
z <- runif(obs, min=0, max=3)
mu <- rnorm(obs, mean=0, sd=2)
nu <- rnorm(obs, mean=0, sd=1)
p <- (b0 - a0 + b2*z + nu - mu)/(a1 - b1)
df1 <- data.frame(
  p = p,
  d = a0 + a1*p + mu,
  s = b0 + b1*p + b2*z + nu
)
rm(p)

#
# OLSで推定してみる
# → バイアスが入る
#
r_ols <- lm(d ~ p, data=df1)


#
# クロスセクションチェック
# → バイアスは同じまま
#
r_ols_css1 <- lm(d ~ p, data=df1[seq(1, obs, 2), ])
r_ols_css2 <- lm(d ~ p, data=df1[seq(2, obs, 2), ])

#
# tslsパッケージを使えば済む操作変数法
# → バイアスは入らない
#
attach(df1)
zm <- matrix(c(rep(1, obs), c(z)), obs, 2)
xm <- matrix(c(rep(1, obs), c(p)), obs ,2)
iv_estimated_a <- solve(t(zm) %*% xm) %*% (t(zm) %*% d)
detach(df1)

#
# 推定結果の中身を比較してみる
#
print_dc <- function(label, a){
  sprintf("%s: D = %.04f%s%.04fp", label, a[1], ifelse(a[2]>=0,"+",""), a[2])
}

paste(c(
  print_dc("OLS", coef(r_ols)),
  print_dc("CSS01", coef(r_ols_css1)),
  print_dc("CSS02", coef(r_ols_css2)),
  print_dc("IV", iv_estimated_a)))

FE-IV練習用データ生成

dとsの式でpが内生変数、zが操作変数、iが個体を表す番号ですが、こんなんで。

noi <- 20 # 個体数
t <- 5 # 観測期間
obs <- t*noi
i <- rep(1:(noi), each=t)
fe <- runif(noi, min=0, max=100)
a0 <- rep(fe, each=t)
a1 <- -1
b0 <- 2
b1 <- 3
b2 <- 4
z <- runif(obs, min=0, max=3)
mu <- rnorm(obs, mean=0, sd=2)
nu <- rnorm(obs, mean=0, sd=1)
p <- (b0 - a0 + b2*z + nu - mu)/(a1 - b1)
d <- a0 + a1*p + mu
s <- b0 + b1*p + b2*z + nu

within変換をかけてIVで推定

within_transfer <- function(x, i){
  m <- tapply(x, i, mean)
  x - rep(m, each=t)
}

w_d <- within_transfer(d, i)
w_z <- within_transfer(z, i)
w_p <- within_transfer(p, i)

zm <- matrix(c(c(w_z)), obs, 1)
xm <- matrix(c(c(w_p)), obs ,1)
estimated_a1 <- solve(t(zm) %*% xm) %*% (t(zm) %*% w_d)

観測数obsが2000ぐらい無いと誤差多し

Stata風に切片項を計算する

estimated_mu <- w_d - estimated_a1 %*% w_p

ma_d <- mean(d)
mi_d <- rep(tapply(d, i, mean), each=t)

ma_z <- mean(z)
mi_z <- rep(tapply(z, i, mean), each=t)

ma_p <- mean(p)
mi_p <- rep(tapply(p, i, mean), each=t)

estimated_a0 <- (d - mi_d + ma_d) - (p - mi_p + ma_p)*c(estimated_a1) - c(estimated_mu)

切片項のP値が欲しい場合

StataのFAQを見ると、within変換をかけた変数に全体の平均値を加算してから推定をすることで、切片項の有意性を出していました。

w_d <- within_transfer(d, i) + mean(d)
w_z <- within_transfer(z, i) + mean(z)
w_p <- within_transfer(p, i) + mean(p)

zm <- matrix(c(rep(1, obs), c(w_z)), obs, 2)
xm <- matrix(c(rep(1, obs), c(w_p)), obs ,2)
estimated_a <- solve(t(zm) %*% xm) %*% (t(zm) %*% w_d)

ゲーム理論で考える、じゃんけんの拡張の数値演算

ゲーム理論で考える、じゃんけんの拡張: ニュースの社会科学的な裏側で使ったコードです。通常のゲーム理論の数値演算で扱われる計算手順に習っていないと言うか、フィーリングで描いたので、何か勘違いがあるかも知れないです。

##########################
### 引き分けになる確率 ###
##########################

p_even <- function(n){
  if(1>n){
    return(0)
  }
  1-(2^n-2)/3^(n-1)
}


##########################
### 確定的確率質量関数計算 ###
##########################

mkdist <- function(p){
  d2b <- function(num, digits=8){
    r <- numeric(digits)
    for(i in (digits-1):0){
      if(2^i <= num){
        num <- num - 2^i
        r[digits - i] <- 1
      }
    }
    return(r)
  }
  
  V <- matrix(0, 2^length(p), length(p)) # 組み合わせ
  pV <- numeric(2^length(p)) # 組み合わせの発生確率
  sV <- numeric(2^length(p)) # 戦略1の数
  for(i in 1:(2^length(p))){
    V[i, ] <- d2b(i-1, digits=length(p))
    pV[i] <- prod((0==V[i, ])*(1-p) + (1==V[i, ])*p)
    sV[i] <- sum(V[i, ])
  }
  tapply(pV, sV, sum) # 戦略1の数ごとの生起確率
}


######################
### 勝利確率の計算 ###
######################

calc_p_win <- function(p){
  p_win_sum <- 0

  # ぐー/ちょき/ぱーを出した他の参加者数ごとに計算していく
  for(num_of_nq in 0:length(p_others)){
  
    # ぐー/ちょき/ぱーで勝つ確率
    if(0==num_of_nq){
      # 自分だけぐー/ちょき/ぱーならば、確実に勝利
      p_win_nq <- 1
    }else{
      # あいこにならなければ、半分の確率で勝利
      p_win_nq <- (1 - p_even(num_of_nq + 1))/2
    }
  
    # きゅーで勝つ確率
    if(2>num_of_nq){
      # 一人がぐー/ちょき/ぱーならば、確実に敗北
      p_win_q <- 0
    } else {
      # あいこになれば、勝利
      p_win_q <- p_even(num_of_nq)
    }

    # 勝利確率
    p_win <- (1-p)*p_win_nq + p*p_win_q

    # ぐー/ちょき/ぱーで負ける確率
    if(0==num_of_nq){
      # 自分だけぐー/ちょき/ぱーならば、確実に負けない
      p_lose_nq <- 0
    }else{
      # あいこになれば敗北
      # あいこ以外でも半分の確率で敗北
      p_lose_nq <- p_even(num_of_nq + 1) + (1 - p_even(num_of_nq + 1))/2
    }
  
    # きゅーで負ける確率
    if(0 == num_of_nq){
      # 全員がきゅーならば、引き分けで負けない
      p_lose_q <- 0
    }else if(1 == num_of_nq){
      # 一人がぐー/ちょき/ぱーならば、確実に敗北
      p_lose_q <- 1
    }else{
      # あいこ以外ならば、敗北
      p_lose_q <- 1 - p_even(num_of_nq)
    }

    # 敗北確率
    p_lose <- (1-p)*p_lose_nq + p*p_lose_q

    # num_of_nqが生じる確率
    s <- as.character(length(p_others) - num_of_nq)
    p_num_of_nq <- dist[s][[1]]
    if(is.na(p_num_of_nq)){
      p_num_of_nq <- 0
    }

    # 期待値調整をして勝利確率を合計する(引き分けは分母から除く)
    p_win_sum <- p_win_sum + p_num_of_nq * p_win/(p_win+p_lose)
  
    if(debug){
      print(sprintf("num_of_nq_of_others: %d (%.3f) p_win_nq: %f p_win_q: %f p_win: %f p_lose_nq: %f p_lose_q: %f p_lose: %f", num_of_nq, p_num_of_nq, p_win_nq, p_win_q, p_win, p_lose_nq, p_lose_q, p_lose))
    }
  }
  p_win_sum
}


####################
### 均衡値の計算 ###
####################

# 拡張じゃんけん参加人数
n <- 5

# 初期値
p_all <- runif(n)

# n*10回ぐらい回せば収束するであろうと言う粗雑な方針
debug <- FALSE
for(c in 0:(n*10)){
  # 最適化前の状態
  print(sprintf("%.5f", p_all))

  # 最適化を行なうプレイヤー
  i <- (c %% n) + 1

  # 最適化を行なうi以外のプレイヤーのpを固定して、
  # キューを選択する人数の確率質量関数を作る
  p_others <- p_all[-i]
  dist <- mkdist(p_others)

  # 勝利確率の最大化を行なう
  r_optimize <- optimize(calc_p_win, c(0, 1), maximum=TRUE)
  p_all[i] <- r_optimize$maximum
}

print(sprintf("%.5f", p_all))
sprintf("最適反応の平均値: %.5f", mean(p_all))


#############################################################
# p_allから負ける人数の期待値を計算し、通常じゃんけんと比較 #
#############################################################
dist <- mkdist(p_all)
expected_num_of_looser <- 0
compared <- (1-p_even(length(p_all)))*length(p_all)/2
for(num_of_nq in 0:length(p_all)){

  s <- as.character(length(p_all) - num_of_nq)
  p_num_of_nq <- dist[s][[1]]

  t <- 0
  if(1<num_of_nq){
    if(1==num_of_nq){
      # 一人だけぐー/ちょき/ぱー
      t <- length(p_all) - 1
    } else {
      p_e <- p_even(num_of_nq)
      t <- p_e*num_of_nq + (1-p_e)*(num_of_nq/2 + length(p_all) - num_of_nq)
    }
    expected_num_of_looser <- expected_num_of_looser + p_num_of_nq*t
  }
}
sprintf("負ける人数の期待値 通常じゃんけん: %.5f 拡張じゃんけん: %.5f",compared, expected_num_of_looser)