平均値の性質を調べる

こんにちは.毛虫が怖い人です.今回は前回とは打って変わって,平均値の性質を調べていきたいと思います.自分のツイッターアカウントのTLにはよく数学関連の話題が流れてくるのですが,そこでふと,様々な平均値に対するいわゆる飛び値の影響の程度がどれほどなのかを調べたいと思いました.ふと思ったのも理由なんですが,「とび値があるときは平均は代表値としての性格が薄くなるからよくない」というようなことを誰でもどこかのタイミングで習いますよね.小学校とか.それを思い出して,幾何平均などの他の平均を用いて代用することは出来るのだろうか?何か基準みたいなのはあるんだろうか?と思ったのも動機です.

今回調べる平均
調べるのに使う平均としては,算術平均(相加平均),幾何平均(相乗平均),調和平均を扱います.定義を書く必要はないと思いますがなんとなくかっこいいので書いておきます.今挙げた順に,

画像1

画像2

画像3

です.ただしnはデータの大きさ,各数値は正であるとします.A,G,Hという添え字で各平均を区別しています.

とりあえず計算してみる
まず,大きい飛び値に対する挙動を調べます.つまり,ひとつだけ大きな数字があった場合に平均がどれくらいの値の変更を受けるかを調べます.最初に思いつく方法としては,変数を二つに固定して,片方を無限に飛ばしたときの大小を比較するという方法ですが,これはうまくいきません.なぜなら,常に(算術平均)≧(幾何平均)≧(調和平均)という関係があるからです.これは一つ目の不等号は特に有名な不等式だと思うので証明はしません(というか証明を知りません).10個の10を用意すると,この平均はどの平均方法でも10です.これを1個だけ100と取り換えてみて,平均が取り換える前と比べて何倍になったかを見てみます.実際に計算してみるとこの数値は

算術平均:1.90倍
幾何平均:1.26倍
調和平均:1.10倍

です.どうやら調和平均が最も飛び値の影響を受けにくそうですね.もう少し数字を変えて実験してみます.具体的には,1~10の(実数)乱数を10個生成し,そのひとつを100と取り換えたときに何倍になっているのかを計算して1000回にわたる"平均"をとります.この"平均"というのは普通の平均,つまり算術平均で,今から三種類の平均について調べるのにそれを使うのは問題があるのではないかと考える方もいると思いますが,

・大まかな性質を調べるためには結局はなんらかの平均をとって代表値を見る必要があること
・飛び値は100に固定し,それ以外の数値についても範囲を絞っているため各データに対して,平均をとって得られる値にそこまで大きなばらつきはないとある程度仮定できること

から,とりあえずは問題ないとします.
手計算でするのはしんどいのでC++でプログラムを書きます.

#include <iostream>
#include <cmath>
#include<fstream>
#include<random>
#include<vector>

using namespace std;

#define rep(i,a) for(int i=0 ; i<a ; i++)

ofstream average_txt("average.txt");

int N; //シード
mt19937 mt(N);
uniform_real_distribution<double> ransuu(1, 10); //範囲

double sanzyutu(vector<double> x) {
   double sum = 0;

   rep(i, x.size()) {
       sum += x[i];
   }

   return sum / x.size();
}

double kika(vector<double> x) {
   double prod = 1;

   rep(i, x.size()) {
       prod *= x[i];
   }

   return pow(prod, 1.0 / x.size());
}

double tyouwa(vector<double> x) {
   double sum = 0;

   rep(i, x.size()) {
       sum += 1.0 / x[i];
   }

   return x.size() / sum;
}


int main() {

   N = 10;
   int L = 1000;
   int n = 10;

   double compare_sanzyutu, compare_kika, compare_tyouwa;

   vector<double> z_sanzyutu(L);
   vector<double> z_kika(L);
   vector<double> z_tyouwa(L);

   rep(i, L) {

       vector<double> x(n);
       vector<double> y(n);

       rep(j, n) {
           x[j] = ransuu(mt);
           y[j] = x[j];
       }
       y[n - 1] = 100.0;

       compare_sanzyutu = sanzyutu(y) / sanzyutu(x);
       compare_kika = kika(y) / kika(x);
       compare_tyouwa = tyouwa(y) / tyouwa(x);

       z_sanzyutu[i] = compare_sanzyutu;
       z_kika[i] = compare_kika;
       z_tyouwa[i] = compare_tyouwa;


   }

   average_txt << "z_sanzyutu " << sanzyutu(z_sanzyutu) << "\n";
   average_txt << "z_kika " << sanzyutu(z_kika) << "\n";
   average_txt << "z_tyouwa " << sanzyutu(z_tyouwa) << "\n";

   return 0;
}

言語はC++です.コードが拙いのはお許しください.コードの中身を簡単に説明します.まず最初の3つの関数は配列を入れるとその算術平均,幾何平均,調和平均を返してくれる関数を定義しています.その次に,大きさ10の配列を用意し,各要素に1~10の乱数(メルセンヌツイスターで生成しています)を入れて,末尾の数字を100と入れ替えたとき,前後で各平均の値が何倍になったかを計算しています.上記コードではその値をcompare_~に代入し,各々の平均による結果をz_~に格納しています.この計算を1000回繰り返し行い,各z_~に格納された値の算術平均を計算します.その値をあらかじめ作っておいたテキストファイルaverage.txtに出力しています.その結果が次の画像です

画像6

この計算結果から一番最初に想像した通り,算術平均が最も飛び値の影響が大きく,幾何平均,調和平均と続くことがわかります.各計算でcompareの値がどうなっているかを見てみます.

int count_sanzyutu = 0, count_kika = 0, count_tyouwa = 0;

を宣言しておいてrep(i,L)のループの中で

       if (compare_sanzyutu > compare_kika && compare_sanzyutu > compare_tyouwa) {
           count_sanzyutu++;
       }
       else if (compare_kika > compare_sanzyutu && compare_kika > compare_tyouwa) {
           count_kika++;
       }
       else if (compare_tyouwa > compare_sanzyutu && compare_tyouwa > compare_kika) {
           count_tyouwa++;
       }

によって1ずつ増やし,この値をテキストファイルに出力させます.その結果が

画像7

です.下半分は当然ですが上に挙げた結果と同じです.ひとつひとつの計算で算術平均のcompareの値が最大になっていることがわかります.

小さい飛び値
前段落とは対照的に100程度の数値の中に1が混じっていた時に平均値がどれくらい小さくなるのかを見ます.前のプログラムで,乱数範囲を95~105にして<vector> y(n)の最後の成分を1に変更しifの比較を反転させます.
念のためint mainの部分だけソースコードを貼っておきます.

int main() {

   N = 10;
   int L = 1000;
   int n = 10;

   double compare_sanzyutu, compare_kika, compare_tyouwa;
   int count_sanzyutu = 0, count_kika = 0, count_tyouwa = 0;

   vector<double> z_sanzyutu(L);
   vector<double> z_kika(L);
   vector<double> z_tyouwa(L);

   rep(i, L) {

       vector<double> x(n);
       vector<double> y(n);

       rep(j, n) {
           x[j] = ransuu(mt);
           if (x[j] == 0) {
               x[j] = ransuu(mt);
           }
           y[j] = x[j];
       }
       y[n - 1] = 1.0;

       compare_sanzyutu = sanzyutu(y) / sanzyutu(x);
       compare_kika = kika(y) / kika(x);
       compare_tyouwa = tyouwa(y) / tyouwa(x);

       z_sanzyutu[i] = compare_sanzyutu;
       z_kika[i] = compare_kika;
       z_tyouwa[i] = compare_tyouwa;

       if (compare_sanzyutu < compare_kika && compare_sanzyutu < compare_tyouwa) {
           count_sanzyutu++;
       }
       else if (compare_kika < compare_sanzyutu && compare_kika < compare_tyouwa) {
           count_kika++;
       }
       else if (compare_tyouwa < compare_sanzyutu && compare_tyouwa < compare_kika) {
           count_tyouwa++;
       }


   }

   average_txt << "count_sanzyutu" << count_sanzyutu << "\n";
   average_txt << "count_kika" << count_kika << "\n";
   average_txt << "count_tyouwa" << count_tyouwa << "\n";

   average_txt << "z_sanzyutu " << sanzyutu(z_sanzyutu) << "\n";
   average_txt << "z_kika " << sanzyutu(z_kika) << "\n";
   average_txt << "z_tyouwa " << sanzyutu(z_tyouwa) << "\n";

   return 0;
}

結果は以下です.

画像8

結果は調和平均の圧勝(?)ですね.今回の場合はcompareの値が小さくなっているほど影響が大きいといえるので「調和平均がもっとも小さいとび値の影響が大きい」ということが出来ます.

数学的な解析
数学的な解析と言っても大したことはしませんが,前までに見たことを式の上から捉え直してみます.
"一般化平均"をpを任意の実数として

画像5

で定義します.ただし,前の段落で計算した時と同じように,各x_iは正であるとします.p=0のときは定義できませんがp→0の極限をとると,以下のように収束するので,この式をp=0のときの定義として採用します.

画像6

これは幾何平均です.つまり,p=0のときの一般化平均は幾何平均に一致します.同様に,p=1の一般化平均は算術平均に一致し,p=-1のときは調和平均に一致します.つまり,共通点の無いように見えた3種類の平均は実はパラメータの違いでしかないことがわかります.さらに,簡単な計算によってp→∞の極限では最大値に,p→-∞の極限では最小値に一致することもわかります.pが小さいほど最小値の影響が大きくなり,pが大きいほど最大値の影響が大きくなると考えれば,前2段落で調べたことが式の上から理解できます.

例外的な挙動
今までに飛び値に対する一般的な挙動を調べました.ただ,それはあくまで"平均的な"挙動であり,数値によっては関係が逆転し得ます.大きい飛び値に対する挙動を調べるときの乱数の範囲を0.001~9にしてプログラムを実行すると以下の結果を得ます.

画像9

15例で調和平均のcompareの値が3つ中最大になっていることがわかります.15例とちょうどいい数なのでデータを表示させてみます.else if (compare_tyouwa > compare_sanzyutu && compare_tyouwa > compare_kika)  の処理のとこに

            rep(j, n) {
               average_txt << x[j] << " ";
           }
           average_txt << "compare_sanzyutu" << compare_sanzyutu << " " << "compare_kika" << compare_kika << " " << "compare_tyouwa" << compare_tyouwa << "\n";

を書き加えればいいです.実行結果は以下です.

画像10

100と入れ替えている値が0.3や0.03とかなり小さいことがわかります.一般的には影響が少ないはずの調和平均で場合によってはcompareの値が10や20と大きく他を引き離しているのが面白いですね.この表示させるプログラムを消して試行回数を増やしてみます思い切って100万回計算させてみます.計算結果は以下です(ちなみに実行時間が22秒かかりました.微妙に長いですね)

画像11

1000回のときの結果と比べて,だいたい1.2%程度の割合で調和平均のcompareの値が3つの中で最大となるような例が存在していることがわかります.さらに,一般的な性質として調和平均は(大きい)飛び値の影響が少ないということが再確認されます.ひとつ興味深いのは,幾何平均は調和平均よりもcompareの値が大きくなりやすく,調和平均のcompareの値が最大になるようなもの1.2%程度の割合で存在しているのに幾何平均についてはそのcountの値が100万回計算してなお0ということです.
小さいとび値については,乱数範囲や代入値を変えていくらか実験しましたが,調和平均のcountが1000のままでした.

さいごに
ここまで読んでいただき有難うございました.この記事を中途半端にだらだらと書いてしまったような気がします.3つの平均がとび値に対してどのように反応するのかはおおまかに調べられたと思いますが,まだ調べられていないことは色々とあります.

・一般化平均のパラメータpを動かした時に,たとえばp=-0.5のとき調和平均と幾何平均の中間の挙動をしめすと考えられるが,おそらく全くの中間ではなくどちらか一方に引っ張られると思うがそれはどちらか
・データが今回のような一様乱数ではなく正規分布や指数分布といった実際にありそうな分布をしているときのとび値に対する挙動
・p=2のときの平均,「根二乗平均」はどのような性質を示すか
・とび値があったときの性質についてもう少し定量的(定性的?)に調べる方法はないか?
・compareの値の大小についてこの領域ではこのcompareの値が最大になるといった領域でデータの大きさ次元(今回の例で言えばn=10)の空間を三分割できるのでは?

などなどです.自分自身もよく分からなくなったので,ここで終わっておきます.何かいいアイデアがあれば,記事を書くなどして教えてください.また,平均値の性質について考察した記事やpdf等があれば,これに紐づけてあるツイッターアカウントのdmや感想などで教えてください.ありがとうございました.

この記事が気に入ったらサポートをしてみませんか?