awkで標準出力から直接データを処理したい(統計編)

# はじめに
お仕事で、データに対してサクッと統計値求めたかったり、規格化したかったりする場合があります。
通常であれば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に規格化
 - 標準化

も追加しました。

[ここ](https://qiita.com/koara-local/items/921a1bf10975d3645512)とか[ここ](http://antibayesian.hateblo.jp/entry/2014/09/15/162605)を参考にさせてもらいました。

※2020/08/10 修正
option受け取りはbash部分を使うけど、基本的にawkの中だけで閉じるように修正しました。

```awk:tokei
#!/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 $@`"
```

説明
以下のような感じで使います。

```shell:使い方1統計値算出
$ 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列目を扱う)

```shell:引数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)/σ )

```awk:使い方2データ整形
$ 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で処理していたのが大分楽になりました。
次は、統計値の次によく使う周波数解析について書きたいと思います。
またコードに不備、改善点があれば教えてください。