確率、期待値とか標準偏差とか…

昨日分散とか書いてるし、たまには人(っていうかTRPGしてる人)の役にたつかもしれないことでも書こうと思い立ちました。最近凝ったダイスの振り方をするシステムをよく見かけます。そのとき期待値と標準偏差ぐらいわかっていたほうがダイスを振りやすいと思うので、すこし探してみたんですが、あんまり使いやすいプログラムが発見できませんでした。僕の探し方が悪いってのもありそうですけど…。そこでその統計の基礎と簡単な統計量を計算するプログラムをつい徹夜で書いてしまった次第です。現実逃避に他ならないなぁ。プログラムは後ろにつけておきました。C++で書かれてますけど、ダイスのところを変えればどんな期待値計算にもつかえるはずです。良かったら使ってみてください。


しかし、まぁ統計なんて勉強したの随分前だし(なにしろさっきまで分散と標準偏差を言い間違えてた…)、基本的にたわごとです。別に難しい言い回しをして、知識を見せつけるつもりはありません(そんな知識ないしね)、要はサイコロ振る前にどれくらい成功しそうなのかを簡単に出せたら便利だねってことなのです。

確率分布

サイコロ一つ振ったときには1から6まで出る確率は一定ですよね(歪んでるとそうならないけど…)。一つ一つの目が出る確率は1/6=0.166...です。

コンピューター上で乱数をつかって再現してみると左のようなグラフになり、たしかにだいたい同じ確率だと分かります。白くてわかりづらい図ですが横軸が出目で縦軸が確率ですね。う〜む我ながらしょぼい図だ。

期待値、標準偏差

期待値:ダイスふったときの出目の平均値です。
標準偏差:平均からのずれの平均ですね。要はダイスの目がどれだけばらけるかです。


この二つの量を使うと振る前にどれくらいの値が出るのかの目安がよく分かります。期待値はだいたいそれぐらいの出目がでて、標準偏差はそれからのずれがだいたいどれくらいなのかを見積もる量なので期待値プラスマイナス標準偏差ぐらいの値がでると思っていいでしょう。簡単なたとえ話をすると1/100の確率で1万円あたるくじと1/10で1000円当たるくじがあるとします。期待値ってのはもらえると期待できる額でどちらも100円ですね。標準偏差に関しては前者は994円で、後者は300円でなのでおおばくちのほうが大きくでることになります。

まずはダイスを何個もふってその合計値を使う方式について考えてみます。
これらの期待値と標準偏差は下の表のとおりになります。

この表をみると一つダイスを振ったときには3.5ぐらいの値がでて5.2から1.8ぐらいのばらつきがありそうだということがみてとれます。

また、表からダイスを増やしても1d6に換算したときの期待値は変わりありません。これは1d6をふって2倍しても2d6をふっても平均的には同じ出目がでるってことで至極当然ですね。
しかし、標準偏差の値はダイスが増えるにつれ小さくなります。つまり1d6を振って2倍したときには平均値は7ですが3.6から10.4ぐらいのばらつきがあるのに対し2d6をふった場合には4.58から9.42ぐらいの中で収まってくれる可能性が高いことを意味しています。

ダイスの数 期待値 標準偏差
1 3.50 1.70
2 7.00 2.42
3 10.5 3.00
5 17.5 3.8
1d6へ換算 期待値 標準偏差
1d6 3.50 1.70
2d6/2 3.50 1.21
3d6/3 3.50 1.00
5d6/5 3.50 0.76
前と同じ横軸出目、縦軸確率の図をいろんなダイスの数で行ったものです。グラフをみると、ダイスの数が大きいほうが平均値周辺に強く集まっていることが分かります。

ソードワールド アリアンロッド

まぁ要はソードワールドをやるときには出目は大体7が出て5−9ぐらいはよくでるってことを覚えておき、アリアンロッドの場合、ダイスが増えたりもしますが基本は以上のような感じで、ダイス増えると期待値によってくるということを覚えておきましょう(クリティカルは??…すみません。疲れたので誰か考えてw)。

あとパーセントでいうとイメージが湧きやすいので2d6で〜以上がでる確率ものっけておきましょう。

2 3 4 5 6 7 8 9 10 11 12
100% 97% 92% 83% 72% 58% 42% 28% 17% 8% 3%

これは簡単に計算できるけど、するのが面倒だったのでソードワールドよもやま話から転載。ほかにもおもしろい話が載ってるのでいってみましょう。7以上で成功なら、6割成功するって覚えるとイメージが湧きやすいですね。また、これの教訓は実力が拮抗してるときほどパラメータをあげる魔法の効果があるってことですね。8以上しか攻撃があたらないときにシャープネスをかけると16%も命中率があがるわけです。10以上しか攻撃があたらないときにかけても11%しかあがらない。まぁ似たようなもんかもしれませんけどね。

特殊なダイスの振り方(ダブルクロス

今までの話はわりとよく知られている話かと思いますけど、ダイスの振り方が複雑になってきたときにはどうなるかというのは、あまりよく知られてないのではないかと思います。今回はまずダブルクロスをとりあげて出目がダイスの数とクリティカルの値によってどう変わるかを調べてみました。

ダブルクロスでは10面ダイスを振り、クリティカル値以上の出目は10扱いとした上で、出たダイス数分を再度振り足し(クリティカル処理)。クリティカルが出なかった場合は、1番高い出目を選択して加算します。

やはり、横軸が出目で縦軸が確率の図です。左の図はダイスの数を変えていて、右の図はクリティカル値を変えています。確率分布の形としては1−10、10−20…そういう区切りの中では何個かのダイスの目の最大をとってくるので。右上がりになります。また、クリティカルになる値そのものでは値をとらないので(つまりクリティカルが10なら最終的な値が10になることはない)そこだけ急激にしぼみます。先ほどの正規分布に近い場合に比べて確率分布がかなり歪んでいます。正規分布の場合より標準偏差から外れた高い値が出る可能性が高いと思います。歪度は調べてないんですが…。

傾向のまとめ

生データを見ながら期待値などについて気づいたことをまとめます。


期待値の半分ぐらいの分散がある。そしてダイス増やクリティカル減なごとに期待値も標準偏差も増える。セッションが進むにつれ目が散るということですね。
同じクリティカル値でダイスをふやすと1から2ぐらい値が増える。同じダイス数でクリティカル値を一つ下げると5ぐらい期待値が増える。期待値は55+ダイスの数-5*クリティカルくらいに計算すると、この範囲(ダイスは5−12個、クリティカル値は7−10)ではあいそうですね。

左に向かう軸がダイスの個数、右側に向かう軸がクリティカル値。縦軸は期待値 左のグラフのデータ点から55+ダイスの数-5*クリティカル値引いたもの。誤差は少ない。

使ったソースコードと計算式

なんとなくC++で書いてしまった。まったく理由なし。自分の好みに他ならないです。STLとかつかわない古いC++な書き方。ちょっと恥ずかしい。
数式とかかかなきゃいいのに…。でもはてなは数式が書けるからつい使いたくなってしまう。
期待値は以下のようにして計算。
[tex: = \sum AQ(A)]
[tex:
]はAという量の平均値、期待値。
[tex:Q(A)]は出目がAになる確率。
標準偏差は以下を展開
[tex:\sigma=sqrt(<(A-
)^2>) ]
以下はソースコードと生データです。
関数ndkはk面ダイスをn個振ったときの出目の合計を
nrzは10面ダイスn個をクリティカル値zでダブルクロス式に降ったときの出目を表します。
この関数を変えるといろんなダイスの期待値を計算できるのでお試しください。

#include<iostream.h>
#include<stdlib.h>
#include<time.h>
#include<math.h>

// range of value 
const int max_range= 300;

int ndk(int n,int k)
{
  int deme;
  deme=0;
  for(int i=1;i<=n;i++) {
  deme = deme+ 1 + (int)(rand()*(double)k/(1.0+RAND_MAX));
  }
  return deme;
}


int nrz(int n,int z)
{
  int deme;
  int dice_num,dice_next;
  int atai_loc,atai_max;
  deme=0;
  dice_num=n;
  while(dice_num>=1) {
    dice_next=0;
    atai_max =0;
  for(int i=1;i<=dice_num;i++) {
    atai_loc = 1 + (int)(rand()*(10.0/(1.0+RAND_MAX)));
    //    cout<<atai_loc<<",";//for debug
     if(atai_loc >= atai_max) atai_max=atai_loc;
     if(atai_loc >= z) dice_next++;		       
  }
  if(dice_next >= 1)atai_max=10;
  deme     = deme+atai_max;
  dice_num = dice_next;
    //cout<<"  ";//for debug
  if(deme >= max_range) return max_range;
  }
    //cout<<"deme"<<deme<<endl;//for debug
  return deme;
}

int main()
{
  int num[max_range];
  double kak[max_range];
    
    const int sikou   = 100000;

    int atai;
    int atai_max;
    double kitaichi,bunsan;
    /*start  initialize */
    srand((unsigned int) time(0));

    for(int j=1;j<=max_range;j++){
      num[j]=0;
      kak[j]=0;
    }
    kitaichi = 0.0;
    bunsan   = 0.0;

    atai_max=0;
    /*finish  initialize */


    /* Main */
    for(int i=1;i<=sikou;i++){
      //      atai= ndk(5,6);
      atai= nrz(12,7);
      if(atai >= atai_max)atai_max=atai;
    num[atai]=num[atai]+1;
    }


    /* Output */
    for(int j=1;j<=atai_max;j++){
      kak[j] = num[j]/(double)sikou;
      cout<<j<<"  "<<kak[j]<<endl;
    }

    
    /* Calculation */
    for(int j=1;j<=atai_max;j++){
      kitaichi=kitaichi+(j*kak[j]);
    }
    for(int j=1;j<=atai_max;j++){
      bunsan =bunsan+(j*j*kak[j]);
    }

    bunsan=sqrt(bunsan-kitaichi*kitaichi);
    cout<<"# "<<"kitaichi"<<"  "<<"bunsan"<<endl;
    cout<<"# "<<kitaichi<<"  "<<bunsan<<endl;

  return 0;
}
生データ

最後に本記事で使ったダブルクロスのダイスに関する統計量の生データです。

ダイスの数rクリティカル値 期待値 標準偏差
5r10 11.5 5.26
5r9 14.7 7.57
5r8 18.62 10.14
5r7 23.94 13.31
6r10 12.17 5.26
6r9 15.98 7.65
6r8 19.94 10.26
6r7 25.61 13.34
7r10 12.76 5.47
7r9 16.60 7.75
7r8 21.13 10.26
7r7 27.16 13.48
8r10 13.26 5.49
8r9 16.60 7.75
8r8 22.22 10.31
8r7 28.58 13.55
9r10 13.74 5.57
9r9 18.07 7.80
9r8 23.11 10.32
9r7 29.79 13.66
10r10 14.18 5.61
10r9 18.70 7.80
10r8 24.07 10.36
10r7 30.97 13.74
11r10 14.57 5.61
11r9 19.27 7.82
11r8 24.77 10.26
11r7 31.87 13.70
12r10 14.95 5.65
12r9 19.82 7.84
12r8 25.43 10.41
12r7 32.84 13.72