身長と体重と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を超えているでしょう。さて、誰かな?

バスト偏差値 修正版!?

偏差値85以上は5000人に1人

日本女性のバストサイズ こちらはしっかり統計されてる。それもRPNカリキュレーターとは、中々オツな事だ。


This year's Index

Home