はじめに
お仕事で、データに対してサクッと統計値求めたかったり、規格化したかったりする場合があります。
通常であればpythonとかRとかでサクッとやれちゃうんでしょうが、データ取得環境がオフラインでパッケージも入れられないような環境だったりしたので、awkでスクリプト作って標準出力のデータをパイプでつなげてサクッと求められるようにしました。
環境
GNU Awk 4.2.1, API: 2.0 (GNU MPFR 4.0.1-p11, GNU MP 6.1.2)
統計な計算
最大最小合計平均3σ
標準出力から直接
- データ数
- 最大値,index
- 最小値,index
- 合計値
- 平均
- 3σ
を求めます。また、データ整形機能
- 最大値で規格化(/max)
- 最小値で規格化(-min)
- オフセット差っ引く(-ave)
- 0~1に規格化
- 標準化
も追加しました。
※2020/08/10 修正
option受け取りはbash部分を使うけど、基本的にawkの中だけで閉じるように修正しました。
#!/bin/bash
# option1 : exec row no
# option2 : '-ave':
# '-max':
# '-min':
# '-stan1':
# '-stan2':
awk -M '\
function ave_stan(data, ave, i, N, T){
for(i=1;i<=NR;i++){
N = split(org[i],T)
for(k=1;k<=N;k++){
if(k==rec)printf"%s ",data[i] - ave
else printf"%s ",T[k]
}
printf"\n"
}
exit;
}
function max_stan(data, max, i, N, T){
for(i=1;i<=NR;i++){
N = split(org[i],T)
for(k=1;k<=N;k++){
if(k==rec)printf"%s ",data[i]/max
else printf"%s ",T[k]
}
printf"\n"
}
exit;
}
function min_stan(data, min, i, N, T){
for(i=1;i<=NR;i++){
N = split(org[i],T)
for(k=1;k<=N;k++){
if(k==rec)printf"%s ",data[i]-min
else printf"%s ",T[k]
}
printf"\n"
}
exit;
}
function stan1(data, min, max, i, N, T){
for(i=1;i<=NR;i++){
N = split(org[i],T)
for(k=1;k<=N;k++){
if(k==rec)printf"%s ",(data[i]-min)/(max-min)
else printf"%s ",T[k]
}
printf"\n"
}
exit;
}
function stan2(data, ave, sig, i, N, T){
for(i=1;i<=NR;i++){
N = split(org[i],T)
for(k=1;k<=N;k++){
if(k==rec)printf"%s ",(data[i]-ave)/sig
else printf"%s ",T[k]
}
printf"\n"
}
exit;
}
NR==1{
rec = min_cnt = max_cnt = 1;
# option setting
split(arg, set)
for(i in set){
if(set[i]!~/[a-zA-Z]/){
rec = set[i]
}
if(set[i]~/-/){
opt = set[i]
}
}
max = min = $rec;
}
{
d[NR] = $rec;
sum += $rec;
org[NR] = $0;
}
min>$rec{
min = $rec;
min_cnt = NR;
}
max<$rec{
max = $rec;
max_cnt = NR;
}
END{
ave = sum/NR;
for(i=1;i<=NR;i++)sqsum+=(d[i]-ave)**2;
sig = sqrt(sqsum/(NR-1));
for(i=1;i<=NR;i++){
z = (d[i]-ave)/sig;
skewsum += z**3;
kurtsum += z**4 - 3;
}
skew = skewsum / NR;
kurt = kurtsum / NR;
if(opt=="-ave")ave_stan(d,ave);
if(opt=="-max")max_stan(d,max);
if(opt=="-min")min_stan(d,min);
if(opt=="-stan1")stan1(d,min,max);
if(opt=="-stan2")stan2(d,ave,sig);
print "#no : ",NR;
print "#max : ",max," no : ",max_cnt;
print "#min : ",min," no : ",min_cnt;
print "#sum : ",sum;
print "#ave : ",ave;
print "#3sig : ",sig*3;
print "#skew : ",skew;
print "#kurt : ",kurt
}' arg="`echo $@`"
説明
以下のような感じで使います。
$ cat gaus.dat | tokei
# no : 10000
# max : 1 no : 4997
# min : 0.367879 no : 1
# sum : 7467.86
# ave : 0.746786
# 3sig : 0.603083
(gaus.datには$exp(-(x/10)^2) (-10<x<10)$のデータが入っています。)
空白区切りの整然としたデータであれば列指定もできます。
(引数無しなら1列目を扱う)
$ cat gaus.dat | tokei 2
指定がなければ1列目が処理されます。
データ整形
引数2はデータ整形できます。
- -ave : 平均値で引く(data[i]-ave)
- -max : 最大値で割る(data[i]/max)
- -min : 最小値で引く(data[i]-min)
- -stan1 : 0~1に規格化( (data[i]-min)/(max-min) )
- -stan2 : 正規標準化( (data[i]-ave)/σ )
$ cat test.dat
0
1
2
3
4
5
6
7
8
9
10
$ cat test.dat | tokei -ave
-5
-4
-3
-2
-1
0
1
2
3
4
5
$ cat test.dat | tokei -stan2
-1.50756
-1.20605
-0.904534
-0.603023
-0.301511
0
0.301511
0.603023
0.904534
1.20605
1.50756
例え1列データであったとしても引数1に列指定はいれなくてはいけません。
⇒入れなくても良くなりました。
最後に
これでイチイチ個人PCにデータを移動してexcelで処理していたのが大分楽になりました。
次は、統計値の次によく使う周波数解析について書きたいと思います。
またコードに不備、改善点があれば教えてください。