俺の果南のセリフがこんなに少ないわけがない

MikuHatsune2016-10-01

ラブライブ! サンシャインを見ていた。視聴前のキャラデザでは圧倒的に松浦果南桜内梨子の圧勝だった。
13話についてはいろいろ思うところがあるだろうが、これまでに視聴していて気になったことがある。
 
『ラブライブ!サンシャイン!!』果南ちゃん出番なさすぎて人気出るか心配! | やらおん!
 
セリフがなかった回が3回もあるし、そもそも休学中という設定で学院での出番が他のキャラより少なくならざるをえないという圧倒的不遇ポジションである。千歌の幼なじみだったはずなのに…
中の人の諏訪ななかも、気にしているのか、6話放送時には

7話放送時には
という発言をしている。
 
というわけで、本当に果南のセリフが少なくて不遇だったかをrstan で解析する。
サンシャイン全13話をひと通り視聴して、セリフの回数をカウントした。セリフを言っている、とは、誰かが喋ったあとにセリフがあるとそれを1回とし、一連のセリフが続いている間はそれがひとつのセリフだとみなした。ただし、場面が変わったり、回想が挟まれたら同一のキャラがしゃべっていても、セリフ回数が1回増えた、とする。
また、3人までなら、同時に返事したりリアクションしたりしたらその3人にセリフ1回カウントとする。2人でランニングしているときの息づかいは難しいので1回のみカウントする。
時間短縮のため2倍速で見ていたのでカウント漏れは多々ある、と思う。
 
とうわけで9人13話分のセリフ回数が以下である。

name	v01	v02	v03	v04	v05	v06	v07	v08	v09	v10	v11	v12	v13
高海千歌	133	115	94	59	102	63	73	55	46	64	53	75	63
桜内梨子	34	65	68	30	87	44	50	34	21	36	19	28	28
渡辺曜	48	39	68	33	20	31	21	23	24	29	72	30	23
津島善子	13	2	3	1	65	21	28	16	12	24	9	10	18
国木田花丸	10	7	5	58	35	18	27	20	10	15	14	12	16
黒澤ルビィ	8	10	9	77	26	39	40	16	23	27	12	24	18
松浦果南	8	5	0	9	0	3	0	13	49	20	16	26	15
黒澤ダイヤ	17	18	11	12	8	24	14	19	24	41	18	31	19
小原鞠莉	1	0	27	9	3	19	4	13	44	22	27	15	15

果南は3, 5, 7話でセリフ0回だった。鞠莉も2話で0回だが、果南の不遇っぷりはやばい。千歌は主人公なのでしゃべりっぱなしである。

 
1話中のセリフ総数に占める割合を見ても、千歌は30% くらいのシェアを誇る。梨子、曜の2年生組で6割近く占める。
3年生は…ダイヤがんばれ。

 
rstan を使って、果南のセリフ回数が最下位になるのが有意になるのかどうかを考えよう。
モデル1 として、各話ごとでのセリフ総数N=\{N_1, N_2, \dots,N_{13}\} に対して、9人のキャラが確率\theta=\{\theta_1,\theta_2,\dots,\theta_9\}, \sum_{i=1}^9\theta_i=1, 0\leq \theta_i \leq 1 を満たすsimplex を用意し、各キャラのセリフ回数s_i について
s_i \sim multinomial(\theta)
としてサンプリングする。
セリフの総数は、アニメの実質放送時間20分くらいに、セリフが生じるとしたらポアソン過程である。パラメータは平均\lambda, 分散\lambda となるので、正規分布にして\sigma を増やさなくていいのでこれでいくことにしよう。
コードはこんな感じになる。このモデルでは各話t ごとに\theta が変わるとしている。
バーンイン 1000、サンプリング 2000、チェイン 3 で10秒以下で推定できる。収束の\hat{R} はすべて1 だった。

# model01
data{
  int<lower=0> N;                       # キャラ人数
  int<lower=0> V;;                      # 話数
  int<lower=0, upper=1000> serif[V, N]; # セリフの数
}
parameters{
  simplex[N] theta[V];                  # 総和1の得票率
  real<lower=0, upper=1000> lambda;     # 1話あたりのセリフ総数の平均
}
model{
  for(v in 1:V){
    sum(serif[v,]) ~ poisson(lambda);
    serif[v,] ~ multinomial(theta[v]);  # 多項分布からサンプリング
  }
}
generated quantities{
  int s[V, N];                          # セリフ数
  int<lower=10> s_all[V];               # 1話ごとの総セリフ数
  for(v in 1:V){
    s_all[v] <- poisson_rng(lambda);
    s[v,] <- multinomial_rng(theta[v], s_all[v]);
  }
}

 
generated quantities を使って、各iter でサンプリングして仮想データを作り、果南が最下位のセリフになる回数が何回あるかカウントすると

  高海千歌   桜内梨子     渡辺曜   津島善子 国木田花丸 黒澤ルビィ   松浦果南 黒澤ダイヤ   小原鞠莉
     0.000      0.000      0.000      0.057      0.003      0.000      0.822      0.000      0.118

13話通して、果南の総セリフ数が9人のなかで最下位になるのは、ベイス的には82% となる。
 
各話ごとに、各キャラがセリフ回数最下位になる確率は以下のとおりである。果南は6話で97.7% とほぼ確実にセリフ最下位である。6話では3回のセリフがあったが、2番目にセリフの少ない花丸で18回もセリフがあるため、果南の存在感が小さくなっている。
セリフ回数0 の3, 5, 7話では、同時に鞠莉もセリフ回数が少ないのでセリフ最下位確率は減るが、それでも80-90% くらいある。

            [,1]  [,2]  [,3]  [,4]  [,5]  [,6]  [,7]  [,8]  [,9] [,10] [,11] [,12] [,13]
高海千歌   0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
桜内梨子   0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.043 0.003 0.036 0.002 0.005
渡辺曜     0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.019 0.016 0.026 0.000 0.002 0.029
津島善子   0.009 0.291 0.199 0.931 0.000 0.006 0.000 0.153 0.393 0.080 0.548 0.542 0.125
国木田花丸 0.027 0.022 0.067 0.000 0.000 0.010 0.000 0.044 0.525 0.563 0.121 0.304 0.196
黒澤ルビィ 0.055 0.003 0.011 0.000 0.000 0.000 0.000 0.164 0.013 0.030 0.204 0.008 0.110
松浦果南   0.047 0.044 0.720 0.033 0.885 0.977 0.935 0.297 0.000 0.187 0.057 0.005 0.233
黒澤ダイヤ 0.002 0.000 0.002 0.008 0.013 0.001 0.001 0.047 0.011 0.001 0.033 0.000 0.076
小原鞠莉   0.859 0.640 0.000 0.028 0.102 0.006 0.065 0.276 0.000 0.109 0.001 0.136 0.227

 
さて、多項分布でモデルを組んだが、別の方法もやってみる。いま、9人13話のセリフ回数をプロットすると、右に裾が長い。バツは中央値である。単位時間にセリフを発する確率を考えるとポアソン分布だが、裾が長いときに過分散を考えるために負の二項分布を用いることがある。
可視化で理解する「負の二項分布」 - ほくそ笑む

 
rstan での負の二項分布は、0 以上の実数\alpha, \beta をパラメータに取り、平均は\frac{\alpha}{\beta}となる。これにより、1話における平均セリフ回数をモデル化し、同様にgenerated quantities で最下位回数を比較する。
こちらのモデルでは話数によって\frac{\alpha}{\beta} は変えていない。
バーンイン 1000、サンプリング 2000、チェイン 3 で40秒で推定できる。収束の\hat{R} はすべて1 だった。

# model02
data{
  int<lower=0> N;
  int<lower=0> V;
  int<lower=0, upper=1000> serif[V, N];
}
parameters{
  vector<lower=0, upper=1000>[N] alpha; # 負の二項分布の平均
  vector<lower=0, upper=1000>[N] beta;  # 負の二項分布の分散
  real<lower=0, upper=1000> lambda;
}
model{
  for(v in 1:V){
    sum(serif[v,]) ~ poisson(lambda);
    serif[v,] ~ neg_binomial(alpha, beta);
  }
}
generated quantities{
  int s[V, N];
  int<lower=10> s_all[V];
  for(v in 1:V){
    s_all[v] <- poisson_rng(lambda);
    for(n in 1:N){
      s[v, n] <- neg_binomial_rng(alpha[n], beta[n]);
    }
  }
}

 
モデル1 よりも果南が最下位になる確率は小さくなった。それでも50% はあるが…

  高海千歌   桜内梨子     渡辺曜   津島善子 国木田花丸 黒澤ルビィ   松浦果南 黒澤ダイヤ   小原鞠莉 
     0.000      0.000      0.000      0.148      0.042      0.004      0.549      0.006      0.251 

 
各話ごとでは、鞠莉、善子、花丸が最下位になる確率が高くなり、果南が最下位になるはずだった確率が分散しているようである。このモデルでは過分散があるため、梨子や曜ですら、1% 以下だが偶然最下位になりうる。

            [,1]  [,2]  [,3]  [,4]  [,5]  [,6]  [,7]  [,8]  [,9] [,10] [,11] [,12] [,13]
高海千歌   0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000
桜内梨子   0.003 0.005 0.005 0.003 0.006 0.003 0.004 0.003 0.004 0.004 0.003 0.003 0.003
渡辺曜     0.004 0.006 0.005 0.005 0.004 0.006 0.005 0.003 0.005 0.004 0.006 0.004 0.005
津島善子   0.197 0.202 0.197 0.204 0.202 0.209 0.203 0.204 0.217 0.224 0.192 0.213 0.217
国木田花丸 0.089 0.081 0.089 0.087 0.084 0.084 0.082 0.086 0.081 0.080 0.083 0.076 0.088
黒澤ルビィ 0.055 0.048 0.045 0.046 0.049 0.049 0.052 0.049 0.045 0.041 0.051 0.046 0.047
松浦果南   0.414 0.413 0.415 0.412 0.420 0.421 0.417 0.426 0.424 0.405 0.434 0.413 0.402
黒澤ダイヤ 0.016 0.017 0.011 0.015 0.014 0.015 0.016 0.013 0.021 0.016 0.012 0.015 0.019
小原鞠莉   0.222 0.227 0.234 0.227 0.221 0.213 0.221 0.215 0.204 0.226 0.219 0.230 0.218

 
rstan でサンプリングされた分布に、9人の13話分のセリフ回数をpoints した。それっぽくなっているような気がする。
果南がもっとも平均\frac{\alpha}{\beta} が小さいが、アニメを見なおしていて思ったのが、意外と善子もセリフは少ないのである。キャラ付けでインパクトが大きかったのである。
曜はメイン回があったので、多くしゃべっているような気がしていたのだが、梨子のほうが実は多い傾向だった。
千歌は圧倒的主人公感。

 
というわけで、果南のセリフが少なかったかというと、有意に少ないというわけではないが、少ないのは少ない、という結果である。
セリフ回数でやっているので、本当ならばすべてのセリフをテキストに起こして、文字数やセリフを言っている時間でやりたかったのだが、11話から見始めて最初の5分くらいで絶望感しかなかったので諦めた。
友利奈緒全セリフデータベースというものがあるので、誰か熱狂的なサンシャインファンがテキストに起こしてないかと期待したけど、なかった。
実際には果南のセリフが0 になる回数を推定すべきだったんじゃね? と思ってセリフ回数が0 になる確率をやっておいたが、モデル1 では3, 5, 7話で50% 程度0になる確率だった。モデル2 では各話で\frac{\alpha}{\beta} が一定なので、常に12% 程度の確率でセリフ回数が0 になるようだった。
 

library(rstan)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())

dat <- t(as.matrix(read.delim("serifu.txt", row=1)))

cols <- c("orange", "pink", "skyblue", "grey", "yellow", "deeppink2", "lightseagreen", "red", "purple")
matplot(dat, col=cols, type="l", lwd=3, lty=1, xlab="話数", ylab="セリフ回数")
legend("topright", legend=colnames(dat), col=cols, lty=1, lwd=3)
barplot(t(dat), col=cols)
par(mar=c(5, 4, 4, 4))
y <- t(dat/rowSums(dat))
barplot(y, col=cols, xlab="話数", ylab="全セリフに占める割合")
p <- par()$usr
text(p[2]-0.7, cumsum(y[,ncol(y)])-y[,ncol(y)]/2, rownames(y), pos=4, xpd=TRUE, col=cols, font=2)

par(mar=c(5, 5.5, 4, 2))
plot(c(dat), rep(1:ncol(dat), each=nrow(dat)), pch=16, col=rep(cols, each=nrow(dat)), xlab="各話ごとのセリフ回数", ylab="", yaxt="n")
axis(2, at=1:ncol(dat), colnames(dat), las=1)
points(apply(dat, 2, median), 1:ncol(dat), pch=4, cex=1.5)

# rstan の準備をしていく
standata <- list(N=ncol(dat), V=nrow(dat), serif=dat)
stanmodel <- stan_model(model_code=model01) # model02 もやる
fit <- sampling(stanmodel, data=standata, chains=3, warmup=1000, iter=2000, seed=1234)
ex <- extract(fit)

# 松浦果南が最下位のセリフ回数か調べる
saikai <- colnames(dat)[apply(mapply(function(z) colSums(ex$s[z,,]), 1:dim(ex$s)[1]), 2, order)[1,]]
table(factor(saikai, colnames(dat)))/dim(ex$s)[1]

res <- mapply(function(z) table(factor(colnames(dat)[apply(ex$s[,z,], 1, order)[1,]], colnames(dat))), 1:nrow(dat))/dim(ex$s)[1]

# 負の二項分布モデル
y <- ex$alpha/ex$beta
colnames(y) <- colnames(dat)
par(mar=c(5, 5.5, 4, 2))
boxplot(y, col=cols, horizontal=TRUE, xlab="平均セリフ回数", main="負の二項分布モデル", las=1, pch=16)
points(c(dat), rep(1:ncol(dat), each=nrow(dat))+0.2, pch=16, col=rep(cols, each=nrow(dat)))