身長と体重とJulia
私の困った解消(emacs)
juliaをemacsから使ってる。julia-modeとjulia-replを入れてね。概ね満足してるんだけど、一点不満がある。
画面が割れてedit-bufferとreplになった時に、行数が等しくなる。編集してる時は、編集面を拡げたいし、replで実験する時は、repl画面が広い方が嬉しい。
今まで汎用性のある画面サイズ変更関数を書いて、それをキーバインドに登録。一発でサイズ変更とポインターの移動をやってた。
それを使おうとしたら、repl画面側では全く作用しない。どうやらreplのterminalがキーシーケンスを握り潰しているみたい。但し、M-x other-window-or-split みたいなのには反応する。
だったら、長い関数名のエイリアスでも登録しておいて楽したい。ぐぐったら Emacs: Use Alias for Fast M-x ぴったりのやつが出て来た。これを参考にして
(defun other-window-or-split () (interactive) (if (one-window-p) (progn (split-window-vertically) (shrink-window 9)) (progn (other-window 1) (balance-windows) (enlarge-window 9)))) (global-set-key (kbd "C-z") 'other-window-or-split) (defalias 'ow 'other-window-or-split) ;; M-x ow
こんなのをinit.elに追加した。自前で定義した関数なんで、元の名前を変えちゃえと言うのは、頷ける事ですが、机の引き出しを増やすって事で。。。
Gnuplotのhist
gnuplotパッケージに付属してるhistで度数分布をさっと書けるんだけど、中々優秀。 分布を調べたい配列を渡すだけで、最小値と最大値から勝手に分割幅を決めて書いてくれる。
julia> hist(x) Gnuplot.Histogram1D([-100.0, 100.0, 300.0, 500.0, 700.0, 900.0, 1100.0, 1300.0, 1500.0], [0.0, 57.0, 26.0, 5.0, 1.0, 1.0, 1.0, 3.0, 0.0], 200.0)
3つの項を持つタプルが返ってきた。初項はX軸用、中項はY軸用、終項は分割幅になってる。確かに、X軸の項差は200だ。
julia> hist(x, bs=100) Gnuplot.Histogram1D([-11.0, 89.0, 189.0, 289.0, 389.0, 489.0, 589.0, 689.0, 789.0, 889.0, 989.0, 1089.0, 1189.0, 1289.0, 1389.0, 1489.0], [0.0, 27.0, 36.0, 18.0, 4.0, 2.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 2.0, 1.0, 0.0], 100.0)
bs=100で分割幅を100に強制してみた。元データは前回から使い回している購入品の価格リストだ。分割幅を100にしたって事は、100円刻みの度数分布にしてくれって注文。残念な事に、半端な端点になってる。
hist(x, range=(0,1500), bs=100, pad=false) Gnuplot.Histogram1D([50.0, 150.0, 250.0, 350.0, 450.0, 550.0, 650.0, 750.0, 850.0, 950.0, 1050.0, 1150.0, 1250.0, 1350.0, 1450.0], [15.0, 42.0, 20.0, 6.0, 3.0, 2.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 2.0, 1.0, 0.0], 100.0)
今度は、rangeを追加して、左端と右端を指定(軸表示の都合上、bsの半分だけずれている)。それからpadをfalseにして、余計なbinを左右に付けないように指定。
julia> count(x->(x<100),x) 15 julia> count(x->(100<=x<200),x) 42
count関数を使って、検算。最初は100円未満、次は100円以上200円未満。histの結果と一致してるね。また、何気に、数学っぽい書き方で、個数を数えちゃったけど、便利ですね。
julia> [v for v in x if v > 1000] 4-element Array{Int64,1}: 1280 1019 1380 1264
こんな内包表記なんかも使えるから、lengthと組み合わせても、同じ事が出来る。
また、histには、nbinsと言うオプションが用意されてる。これは、何本の棒にするかって指定だ。統計の本とかだと、データがn個有る時の、棒の数の目安は、1 + log2(n) ぐらいが良いとされている。実際に棒の数を変えてみると、説明のし易い形になったり、ぼやけた形になったりする。騙されないようにしよう。
StatsBase.fit
histがどういう実装になってるか紐解いていく。マルチメソッドで、同名関数が2つあるけど、引数が少ない方を見る。(Histogram1Dの方ね。同様にHistogram2Dが有るけど、こちらは結果がヒートマップで表示される。何かの時に役立ちそう。)
すると、fitなんてのが核になっている事が分かった。
冒頭で、StatsBaseをusingしてるので、多分これだろうと消去法で当たりを付ける。だって他は、色の扱いっぽいものとREPLなんだもの。
使うに先立って、PkgからStatsBaseを登録して、usingしておく。Gnuplotで使ってるから共用させてよってのは、出来ない相談。地獄を避けるためだ。
julia> h = fit(Histogram, x, 0:100:1500) Histogram{Int64,1,Tuple{StepRange{Int64,Int64}}} edges: 0:100:1500 weights: [15, 42, 20, 6, 3, 2, 1, 0, 1, 0, 1, 0, 2, 1, 0] closed: left isdensity: false
ビンゴでしたねぇ。一発で的中したけど、pythonみたいにパッケージのヒントが付いていないので、それらしいのが沢山あると、苦労しそう。
数字の偏り
ある本に、世の中で一番出現する数字は1だ。以下、2,3,4,5,6,7,8,9 と続くなんて事が説明されてた。暇な人が新聞とかから拾い出して統計を取ったらしい。にわかに信じられんな。
今回は元データをスーパーのレシートから入力した。その時、スーパーって99とか198とか特有の数字が好きだなと思ったものだ。消費者心理を狙ったんですかね。
折角だから、暴いてみるか。まずは、一番下の桁。
julia> using StatsBase julia> counts( x .% 10 ) 10-element Array{Int64,1}: 17 1 4 0 5 5 3 3 18 38
おあつらえ向きな関数がStatsBaseに用意されてる。出現頻度を数えてくれるんだ。前に出て来たcountは、単数形なんで一つしか数えられない。countsは複数形なんで、一発で複数の出現頻度を数える。
与えた引数は、% を使って1の桁だけを抽出。よく見ると、%の前にポイントが付けられている。これがあると、xの中身の一つ一つに対して演算してねって依頼になる。
結果は0の出現回数は17回、1は1回、、、9は38回とダントツに多い。
julia> counts( x .% 10, 7:9 ) 3-element Array{Int64,1}: 3 18 38
countsの第二引数にレンジを与えると、算出範囲を限定出来たりする。
julia> c = counts( div.(x, 100) ) 14-element Array{Int64,1}: 15 42 :
今度は100円区切りでカウント。何番目(何円台)かを付加してみる。
julia> [(i-1,c[i]) for i in 1:14] 14-element Array{Tuple{Int64,Int64},1}: (0, 15) (1, 42) (2, 20) (3, 6) (4, 3) (5, 2) (6, 1) (7, 0) (8, 1) (9, 0) (10, 1) (11, 0) (12, 2) (13, 1)
下記のようにやってもよい。
julia> for i in 1:14 @show i-1, c[i] end (i - 1, c[i]) = (0, 15) (i - 1, c[i]) = (1, 42) (i - 1, c[i]) = (2, 20)
ll = open("real_buy.txt") do fp readlines(fp) end cc =join(ll) cn = split(cc,"") nn = parse.(Int,cn) counts(nn) 10-element Array{Int64,1}: 24 52 35 23 14 14 12 21 24 52
最後に、各数字の出現回数を計測してみた。実験しながらね。 価格を文字列の配列として読み込み(ll)。結合して1つの文字列に変換(cc)。一文字づつの文字配列に変換(cn)。それをIntに変換(nn)。最後に数字の出現頻度を算出。9,1,2の順になってるなあ。
スーパーは、9と言う数字が大好きなんですね。
身長と体重
以前構想したBGI、Gはグラマーな事ね。それにはB,W,Hが必要。そんなデータはワコールとかトリンプにしかないぞ。実データが無いと楽しめないし、中ピ連からクレームが来るかも知れないので、涙を飲んで断念。
BMI計算の材料になる、身長と体重なら、容易に入手出来そうだから、BMIのシュミレーションで我慢しとく。何処にデータが有るかな?
データを求めて
身長5cm単位の分布図 うん、正に正規分布だね。 身長の分布は本当に正規分布に従うのか!? 統計に関する説明が有った。 身長偏差値チェッカー こんな便利なのもある。みんな統計情報って好きなのね。
上で紹介されてた、統計で見る日本 で、EXECLファイルが落とせるんだけど、おいらそんなの持っていないぞ。 まてよ、 libreofficeで開けるかな?
h26_hoken_toukei_02.xlsx
サフィックスの最後にxが付いているのが味噌なんだな。ちゃんと開けて閲覧出来た。けど、欲しいデータに変換するのが超面倒。それに、あのボタン沢山のパネルには、卒倒させられるぞ。よくあんなものを(仕事とは言え)使ってたものだな。
ここは、えいやっと、
Kind | mean | std |
---|---|---|
Hight | 170 | 6 |
Weights | 63 | 10 |
こういうデータを使おう。東京農大の保健室が、 身長・体重・BMIの平均値と標準偏差 なんてのを公開してくれていたので、有難く使わせてもらった。
正規分布のお勉強
データの統計量(Statistics) って事で解説されてた。 そして、一般式をJuliaに展開されてた。 正規分布
ようするに、この式って確率密度関数 な訳ね。
snd(x,m,s) = (1/sqrt(2*pi*s^2)) * exp(-((x-m)^2 / (2*s^2))) nd(x) = snd(x,0,1) hnd(x) = snd(x,170,6) wnd(x) = snd(x,63,10)
ベルカーブ(正規形)を計算する式を定義。mは平均値、sは標準偏差の積り。その一般式を使って、ndは標準形のカーブ。hndは、身長のカーブ。wndは体重のカーブを定義した。(haskellで言う所のカリー化みたい)
統計学では平均値の事を当たり前のように、μと表す。標準偏差はσだ。juliaは数学者にも平気で使って貰えるように、変数名とかにこれらのギリシャ文字を使える。
emacs-modeでも、 \mu
とか入力してからTABを叩けば、μに変換出来る。ただ、残念な事にCUIモードで使うemacsでは、誤動作する。そう、ひと昔の某editorみたいに、byte単位でしか扱えないやつのごとくだ。カーソル位置の制御が正しくない。ユニコード文字が泣き別れになって、文字化けとか発生。ゆえに、涙を呑んで、ありきたりの文字を採用した。残念至極。
julia> x = 150:190 150:190 julia> @gp x [hnd(y) for y in x] "w l"
これが実際に身長のカーブを描く例。150から190cmの範囲に渡って、出現頻度をグラフにしてみた。 同様に体重のカーブを描いてみると、幅広になった。標準偏差が大きいと、幅が広がる(要するに、バラツキが大きい)のが伺える。
どうやって発生させる?
さて、次は、身長と体重をランダムに発生させて、そのデータからBMIを計算させればいいな。 そんな事出来るのか? 取り合えず using Random だな。
randn([rng=GLOBAL_RNG], [T=Float64], [dims...]) Generate a normally-distributed random number of type T with mean 0 and standard deviation 1
randnってのが、偏った比率で乱数を発生してくれるとな。本当か検算してみる。
julia> q = randn(3000) 3000-element Array{Float64,1}: -0.5795832062760665 -0.0897716967411955 : julia> using Statistics julia> mean(q) -0.023806058060213582 julia> std(q) 0.9946257193967464 julia> minimum(q) -3.202317530254624 julia> maximum(q) 4.013185378506785
平均が0、標準偏差が1。範囲は-3から4ぐらい。
julia> h = hist(q) Gnuplot.Histogram1D([-4.5, -3.5, -2.5, -1.5, -0.5, 0.5, 1.5, 2.5, 3.5, 4.5, 5.5], [0.0, 3.0, 64.0, 401.0, 1057.0, 1033.0, 376.0, 61.0, 4.0, 1.0, 0.0], 1.0) julia> @gp h
一応目視確認。ベルカーブになってるね。でも幅の拡がりが±3ぐらいしかなくて、これじゃ、身長や体重の代用になりそうにないな。どうする?
q .+ 170
とかすれば、中心を並行移動出来るけど、拡がりはどうする。元データの標準偏差が1でこれが拡がりを表しているんだから、単純に掛け算しておけばいいのか。 先に拡げて、それを並行移動だな。
julia> r = q .* 6 julia> z = r .+ 170 julia> h = hist(z) Gnuplot.Histogram1D([147.5, 152.5, 157.5, 162.5, 167.5, 172.5, 177.5, 182.5, 187.5, 192.5, 197.5], [0.0, 24.0, 124.0, 458.0, 919.0, 899.0, 439.0, 121.0, 13.0, 3.0, 0.0], 5.0)
うん、これでよさそうだ。一応検算って事で、meanとstdを取ってみたら、指定した通りになってた。
この方法を思いつたのは、正規形のやつの平均が0で標準偏差が1って点。両数字とも、特別な値だ。0は加算の単位元、1は乗算の単位元。
何も考えずに加算すれば、平均値がずれる。何も考えずに乗算すれば、標準偏差を簡単に変更できる。若し、元データ(乱数)が、それ以外になってたら、それを考慮して演算しなきゃいけない。そういう意味では、0と1ってのは、良く吟味された値だと思うよ。数学者の怠慢に乾杯(誉め言葉です、念の為)
後は、これを体重にも適用すれば、シュミレーション用のデータが完成。
実際にシュミレーションする
bmi(h,w) = w / (h/100)^2 reqrn(m,s,n=1) = (randn(n) .* s) .+ m
reqrnは、平均値と標準偏差と個数(省略時は1)を指定して、乱数を発生させる関数だ。
ht = reqrn(170,6,3000) wt = reqrn(63,10,3000) b = [bmi(ht[i],wt[i]) for i in 1:3000] h = hist(b) Gnuplot.Histogram1D([9.0, 11.0, 13.0, 15.0, 17.0, 19.0, 21.0, 23.0, 25.0, 27.0, 29.0, 31.0, 33.0, 35.0], [0.0, 10.0, 39.0, 118.0, 294.0, 508.0, 600.0, 583.0, 416.0, 256.0, 118.0, 38.0, 20.0, 0.0], 2.0)
出来た。
julia> mean(b) 21.873364531202803 julia> std(b) 3.801185631721734 julia> maximum(b) 33.99864944391385 julia> minimum(b) 10.285352614719926
実データと比較してみると、まあ再現出来ているな。
BMIの極限値
上でBMIのシュミレーションをしたけど、極限値はどうなるだろう?
極大になるには、分子が体重が大きくて、分母の身長が小さければ良い。でぶでチビと言う取合せ。極小はその逆だから、瘦せでノッポさん。
julia> bmi(170-6*3, 63+10*3) 40.25277008310249 julia> bmi(170+6*3, 63-10*3) 9.336803983703033
平均から3σ離れた人は、1000人中に1人ぐらいしか現れない特異な人。身長も体重も何の相関が無いとして極端と極端を組み合わせると、WHO真っ青、厚生省の役人も飛んでくるような値になりました。
bmiの計算式を見てると、体重を定数と見れば、身長の2乗に反比例してる。体重を固定して、身長を変化させた時、どんなグラフになるか書かせてみるか。
2D and 3D グラフ描画
Gnuplotで一つのグラフに複数の折れ線を入れる方法 Plot two curves: として、例が載ってた。これを応用すればいいんだな。
x = 150:190 @gp "set grid" @gp :- x [bmi(h,65) for h in x] "w l" @gp :- x [bmi(h,85) for h in x] "w l" @gp :- x [bmi(h,45) for h in x] "w l"
これで、身長を150から190まで振った時で、3種の体重の線を書けた。
次は3Dグラフだな。これだと、全体像が分かる。
julia> x = 150:5:190 150:5:190 julia> y = 45:5:85 45:5:85 julia> z = [bmi(h,w) for h in x, w in y] 9×9 Array{Float64,2}: 20.0 22.2222 24.4444 26.6667 … 31.1111 33.3333 35.5556 37.7778 18.7305 20.8117 22.8928 24.974 29.1363 31.2175 33.2986 35.3798 : 13.1483 14.6092 16.0701 17.531 20.4529 21.9138 23.3747 24.8356 12.4654 13.8504 15.2355 16.6205 19.3906 20.7756 22.1607 23.5457 julia> @gsp x y z "w l"
x軸に5cm刻みの身長を割り当て。y軸は5Kg刻みの体重を割り当て。z軸方向はbmi値を計算させた。後は、@gspで、サーフェイスグラフを描くだけ。例によってマウスでぐりぐりとグラフを回せるぞ。
ちょっとおまけ。シュミレーション用のデータとして正規分布の乱数を 注意して 使った。現実に即してね。何も考えないと、シュミレーションなら(普通の)乱数でいいじゃんとなる。
rand(150:190, 3000)とかやると整数の乱数が返ってきちゃう。それは、あんまりだろう。小数点付きで欲しい(ちょっと知恵が回った状態)。さあ、どうする?
julia> q = (rand(3000) .* (190 - 150)) .+ 150 3000-element Array{Float64,1}: 171.3243907166107 155.6558037805561 :
本当にまんべんなく散らばっているか? 例によって、検定
julia> mean(q) 170.06559037144146 julia> std(q) 11.486356303816363 julia> minimum(q) 150.00071148280264 julia> maximum(q) 189.99112901304898
よさそうだけど、まだ信用ならん。やっぱり度数分布を取ってみなければ安心出来ない。
julia> counts(q, 150:190) ERROR: MethodError: no method matching counts(::Array{Float64,1}, ::UnitRange{Int64}) Closest candidates are: counts(::AbstractArray{T,N} where N where T<:Integer, ::UnitRange{T} where T<:Integer) at /home/sakae/.julia/packages/StatsBase/unDUx/src/counts.jl:79 :
入力はIntしか受け付けないんだな。どうやってFloatをIntにする。ぐぐれ。
julia> qi = floor.(Int, q) julia> @gp 150:190 counts(qi, 150:190) "w l"
多少のバラツキはあるけど、出現頻度はほぼ同じになった。明らかにベルカーブとは違う。
julia> rand(150.0:0.01:190.0, 5) 5-element Array{Float64,1}: 181.29 156.6 165.44 189.07 175.62
ああ、自前でやらなくても、レンジを上手に使えばいいんだよ。これだから原始人は困るよ。
正規分布な乱数を自前で発生
一様乱数の平均と標準偏差を、まず求めてみる。
julia> q = rand(3000) julia> mean(q) 0.5012587887696258 julia> std(q) 0.2918537919539973
randnとは、明らかに違うな。一様乱数なら、/dev/urandomに元を求めたり、最近流行りのメルセンヌ乱数発生機構を使ったりして、容易に発生出来る。
じゃ、正規分布で出現頻度が決まる、randnはどうやって発生させてるの? ぼんくら頭ではちっとも思い付かないので、奥村先生の「C言語によるアルゴリズム辞典」を引いてみた。 3種ほど、紹介されてた。
julia> myrn() = sum( rand(12) ) - 6.0 myrn (generic function with 1 method) julia> myrn() -1.63156920600822 julia> myrn() 1.3076474965179115
一様乱数を12個加算すると、平均は6近辺になる(多数の乱数を加算すると、中央値に収束するという、大数の法則)そこから6を減算すれば、正規分布っぽくなるというのの利用。
julia> z = [myrn() for i in 1:3000] 3000-element Array{Float64,1}: -0.3220261324937095 0.7637254337971431 : julia> mean(z) 0.02718671470759833 julia> std(z) 1.0095003997132443
うん、それっぽくなった。
小遊三さん風に
やっぱり、三遊亭小遊三さんみたいにBWHが気になる。調べる良い手を思い付いた。
ワコールなりの、お客様サポートセンターへ電話するのさ。男のオイラーが電話すると、怪しまれるので、女房にお願いする。
うちの30の娘(嘘も方便)が、最近コロナの影響で、運動不足がたたり、体形が醜くなってきたと申しております。 本人は非常に気にしてまして、BWHの標準と比べてみたいと言ってます。
そしたら、統計学を多少かじったおとーさんが、平均を教えてもらうだけじゃ駄目だと言ってます。何でも、偏差値を算出しないと意味が無いそうで、それには標準偏差もなければならないそうです。
私には難し過ぎてさっぱりですか、おとーさんはBWHはベルカーブになるんだろうか? ひょっとして二項分布かもとか言ってます。昔は、歪度が0だったけど、今はだらけてきて大きくなってしまったなんて言ってますけど、分かりませんよね。
兎に角、平均値と標準偏差が分かれば何とかなりそうなので、宜しくお願いします。
偏差値ってテストの点だけの評価以外にも使えるぞ。
h(x) = 50 + 10 * (x - m) / s
丁度、標準偏差分だけ平均値からずれていれば、偏差値は 50 ± 10 って事になる。
某TV局に出演してる気象予報士のおねーさんは、偏差値が75を超えているでしょう。さて、誰かな?
日本女性のバストサイズ こちらはしっかり統計されてる。それもRPNカリキュレーターとは、中々オツな事だ。