ShellScript: rs（reshape）コマンドを利用し，場合分けなしで（ifを使わず）中央値を求める．

# 要約
　データを1列に並べる．ソートする．それぞれのデータを複製して2つずつにする．`rs`で2列にする．1列目の最後データと2列目の頭のデータとの平均が中央値である．

```zsh:中央値を求める
% cat test.csv \
| tr ',' '\n'      # 1列に並べる
| sort -n \        # 昇順に並べる
| sed  'p' \       # 複製
| rs -t r 2 \      # 列方向2列
| awk '(NR==1){n1=$2}END{n2=$1; print (n1+n2)/2}' # 2つの数の平均

```


# はじめに
- 中央値の定義は以下[[1]]．
- 具体例1：5つの数（1, 2, 3, 4 ,5 ）の中央値は3．
- 具体例2：4つの数（1, 2, 3, 4）の中央値は2と3の平均の2.5．
- `awk`などを用いて簡単に求まるが，基本的には場合分けを要する[[3]][[4]][[5]]．
- (1)データの個数を数える，(2)場合分けをする，という作業のため，パイプラインで左から右への処理をしづらく，煩わしく感ずるのだと思う[[2]]．
- 文字列を並べ替える`rs`を利用して，左から右へのデータの流れで中央値を求めてみた．
  
> 中央値（中央値、英: median）とは，代表値の一つで，有限個のデータを小さい順に並べたとき中央に位置する値．たとえば5人の人がいるとき，その5人の年齢の中央値は3番目に年寄りな人の年齢である．ただし，データが偶数個の場合は，中央に近い2つの値の算術平均をとる．（https://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E5%80%A4
中央値 - Wikipedia）

# 環境
- macOS: 10.15.4
- zsh: 5.7.1

# 準備

#### (0) 紙とペンを用いた方法．
　9個（奇数個）の数の中央値の求める方法を考える．9個の数を小さい順に1列に紙に書く．1番小さい数と1番大きい数が重なるように紙を1回折る．紙を広げて折り目がついている数が中央値である．同様に，10個（偶数個）の数の場合を考える．折り目に最も近い2つの数の平均が中央値である．これと同じことを`rs`を利用してやる．


#### (1)`jot`コマンドで練習用のデータを準備する．

```zsh:jot使用例．オプション-rで乱数を作成している．
% jot -r 3 
54
4
34
```

#### (2)データを1列に並べる．
- jotコマンドでは勝手に1列に並ぶので省略．
- 通常は`tr`や`sed`で区切り文字を改行に置換して1列に並べる．

```zsh:置換を利用して1列に並べる．
echo '1 2 3' | tr ' ' '\n'
1
2
3
```

#### (3) 数を昇順に並べる．
- `sort -n`を使う．
- 例えば"11"を"1"と"1"との文字列ではなく，数値の"11"として読むのに，`-n`が必要．

```zsh:
% echo '1\n2\n11'
1
2
11
% echo '1\n2\n11' | sort
1
11
2
% echo '1\n2\n11' | sort -n
1
2
11
```


#### (4) それぞれの数値を複製する．
- `sed 'p'`を使う．
- `sed`の`p`コマンドは "print"の意味．`p`だけの場合，入力行をそのまま出力する．
- `sed`は全ての入力行を出力するというのがデフォルトの操作[7]．同じデータが2つ並ぶ必要はないので，通常は`-n`で入力行の自動出力を抑制する．今回はあえて`-n`をつけず，2つ並ばせる．

```zsh:
% echo '1 2' | sed 'p'
1 2
1 2

% echo '1 2' | sed -n 'p'
1 2
```

#### (5) 縦方向で2列にする．
- `rs`を用いる．
- `-t`で縦方向にする．
- `-C`で区切り文字を指定しない場合，スペース` `2個が区切り文字となる．
- `r 2`とすると，`行指定なし，2列`となる．

```zsh
% jot 5 | rs -t r 2
1  4
2  5
3  
```

#### (6) 1列目の最後の数と2列目の最初の数の平均をとる．
- 様々な方法が考えられる（下記）．

# 手順概要
- データを1列に並べる（`tr ' '\n'`)
- 昇順に並べる（`sort -n`）
- 複製（`sed 'p'`）
- 列方向2列（`rs -t r 2`）
- 1列目の最後の数と2列目の最初の数との平均（`awk`など）．


# rsコマンドを利用して中央値を求める．
### 数値の個数が奇数の場合．
- `jot 5`で1から5の数値を準備する．中央値は3． 
- 下記のように，1列目最後と2列目の最初の数値の平均で中央値が求まる．

```zsh
%jot 5 | sort -n | sed  'p' | rs -t r 2
1  3
1  4
2  4
2  5
3  5
```

### 数値の個数が偶数の場合．
- `jot 6`で1から6の数値を準備する．中央値は3と4の平均の3.5．
- **奇数の時と同じやり方**，すなわち1列目最後と2列目最初の数の平均で中央値を求めえることができる．
- すなわち，<font color="red">"要素数を数える作業"と"偶奇の場合分け"とを回避できた</font>．

```zsh
%jot 6 | sort -n | sed  'p' | rs -t r 2 
1  4
1  4
2  5
2  5
3  6
3  6
```

# 1列目の最後の数と2列目の最初の数を取り出して平均をとる．
#### awkを用いた方法．
- これが一番素直でなかろうか．
- 変数`n1`と`n2`にそれぞれの数を代入し，`END`ブロックで計算．
- 以降は見通しを良くするため適宜改行する．

```zsh
% jot 6 \
| sort -n \
| sed  'p' \
| rs -t r 2 \
| awk '(NR==1){n1=$2}END{n2=$1;print "median: "(n1+n2)/2}'
median: 3.5 # 結果
```

#### 必要な数だけ取り出して計算は他でする．
- 上記の`awk`を用いた方法では変数に代入する際，視線はの動きは右から左である．
- 今回このようなことを試みた動機としては，"左から右への流れのまま中央値を求めたい"であったので，変数に代入する際の右から左への動きがなんとなく嫌である．
- そこで，数値を取り出すだけして，計算は後で行う方法を考えた．

##### awkで2つの数を取り出す．

```zsh
# awkを用いて必要な数だけ取り出す．
% jot 6 \
| sort -n \
| sed  'p' \
| rs -t r 2 \
| awk '(NR==1){print $2}END{print $1}'
4 # 2列目の最初の数
3 # 1列目の最後の数
```

##### sedで2つの数を取り出す．
- 上記の`awk`の方法では`(NR==1)`で1行目の情報を得て，`END`ブロックで最終行の情報を得ていて，非対象でなんとなく嫌である．
- そこで，2列に並べた文字列をさらに行方向に線対称に回転させる（`tail -r`または`tac`）．
- さらに，`tr -s`で区切り文字を改行に置換（`-s`は連続する文字を置換するする際に用いる）．すると1行目と最終行が目的の数となる．それをsedで取り出す[[8]]．

```zsh
% jot 6 \
| sort -n \
| sed  'p' \
| rs -t r 2 \
| tail -r \
| tr -s ' ' '\n' \
| sed -n '1p;$p'
3 #結果
4 #結果
```

#### 取り出した2つの数を用いて計算する．
##### 逆ポーランド記法
- 左から右の流れで計算できる方法，すなわち後置記法（逆ポーランド記法）を使ってみた．
- 逆ポーランド記法は`dc`で行える．
- 末尾の`p`は"print"の意味らしい．
- 小数点以下をどこまで表示するか（精度）を`k`で指定せねばならないらしい．
- 今回は0以上の整数のみを扱うので小数点第1位まで表示する．

```zsh:dc使用例
echo '1 2 + p' | dc 
3 # 結果

% echo '1 2 + 2 / p' | dc
1 # 小数点以下が切り捨てられる．

echo '1 2 + 1k 2 / p' | dc
1.5 # 1kオプションで小数点第一位まで表示された
```
- 取り出した2つの数の後ろに`+ 1k 2 / p`を追加した文字列を`dc`コマンドにパイプで渡す．

##### 文字列の末尾に文字列を追加する．
- 様々な方法がある．例として以下を挙げる．

**その1:<<()を用いる**

```zsh
% jot 6 \
| sort -n \
| sed  'p' \
| rs -t r 2 \
| tail -r \
| tr -s ' ' '\n' \
| sed -n '1p;$p' \
| <<(echo '1k + 2 / p') | dc
3.5 #結果
```

**その2:ヒアストリング`<<<`を用いる**

```zsh
~~略~~
| <<< '+ 1k 2 / p' | dc
```

**その3:sedを用いる**
- 最終行の末尾に文字列を追加する．
- sedの区切り文字`/`と割り算の`/`が同じでエラーがでる．前者を別の文字にするか，後者をバックスラッシュ`\`でエスケープする．

```zsh
~~略~~
| sed '$ s/^.*$/& + 1k 2 \/ p/' | dc
```

**その4:sed a（add）を用いる**
- `a`（addの意味）を用いて最終行の下に行を追加．
- macの`sed`場合，`a`コマンドの後に改行が必要[9]．
- linuxの`sed`では改行が不要のようである[[10]][[11]]．
- ただし，以下の記事を参考にしてやると，1行で書けた[[12]]．
（https://qiita.com/kkdd/items/725e53572bc69e4b51b7
sed による置換で改行\nを出力する - Qiita）



```zsh
~~略~~
| sed '$a \'$'\n + 1k 2 / p' | dc
```

```zsh
# MACのsedではaコマンドは通常は改行が必要
~~略~~
| sed '$ a \
 + 1k 2 / p'
```

# 考察
- 中央値の求め方はしばしば話題になる．しかし，`if`を用いない方法は見かけなかった．そこで，今回やってみた．

- 四分位数も同様の操作で求めることができる．ただし，四分位数は定義が複数あり，ややこしくなるため今回は扱わなかった．

- `awk + if`を用いた通常の方法と比較して，1.5倍時間がかかる．`rs`にどうしても時間がかかる．100万個のデータで約5秒要する．

# まとめ
- `rs`を利用し，場合わけせずに中央値を求めた．




# 参考
\[1]https://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E5%80%A4
中央値 - Wikipedia
\[2]https://stackoverflow.com/questions/41750008/get-median-of-unsorted-array-in-one-line-of-bash
Get median of unsorted array in one line of BASH - Stack Overflow．
\[3]https://orebibou.com/2016/03/linuxunix%E3%81%AE%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%81%A7%E5%B9%B3%E5%9D%87%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%83%BB%E6%9C%80%E5%A4%A7%E3%83%BB%E6%9C%80%E5%B0%8F%E3%82%92%E6%B1%82/
Linux/UNIXのコンソールで平均・中央値・最大・最小を求める | 俺的備忘録 〜なんかいろいろ〜
\[4]https://stackoverflow.com/questions/6166375/median-of-column-with-awk
bash - median of column with awk - Stack Overflow
\[5]https://unix.stackexchange.com/questions/13731/is-there-a-way-to-get-the-min-max-median-and-average-of-a-list-of-numbers-in
bash - Is there a way to get the min, max, median, and average of a list of numbers in a single command? - Unix & Linux Stack Exchange
\[6]https://kunst1080.hatenablog.com/entry/2014/02/15/180842
jotコマンドについて調べてみた - くんすとの備忘録
\[7]Dale Dougherty, Arnold Robbins,1997年，sed & awkプログラミング 改訂版，O'REILLY，福崎俊博（訳），p21，2.3.3.2 入力行の自動表示の抑制．
\[8]http://shuzo-kino.hateblo.jp/entry/2015/09/17/235436
sedで最初と最後の行を表示する - Bye Bye Moore
\[9]Dale Dougherty, Arnold Robbins,1997年，sed & awkプログラミング 改訂版，O'REILLY，福崎俊博（訳），p97，5.5 追加，挿入，変更．
\[10]https://qiita.com/tukiyo3/items/f75561a0cfbb7c2eedc9
sedで最終行に挿入 - Qiita
\[11]https://orebibou.com/2016/07/sed%E3%81%A7%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%9C%80%E5%88%9Dor%E6%9C%80%E5%BE%8C%E3%81%AE%E8%A1%8C%E3%81%AB%E6%8C%BF%E5%85%A5%E8%BF%BD%E8%A8%98%E3%81%99%E3%82%8B/
sedでファイルの最初or最後の行に挿入(追記)する | 俺的備忘録 〜なんかいろいろ〜



[1]:https://ja.wikipedia.org/wiki/%E4%B8%AD%E5%A4%AE%E5%80%A4
[2]:https://stackoverflow.com/questions/41750008/get-median-of-unsorted-array-in-one-line-of-bash
[3]:https://orebibou.com/2016/03/linuxunix%E3%81%AE%E3%82%B3%E3%83%B3%E3%82%BD%E3%83%BC%E3%83%AB%E3%81%A7%E5%B9%B3%E5%9D%87%E3%83%BB%E4%B8%AD%E5%A4%AE%E5%80%A4%E3%83%BB%E6%9C%80%E5%A4%A7%E3%83%BB%E6%9C%80%E5%B0%8F%E3%82%92%E6%B1%82/
[4]:https://stackoverflow.com/questions/6166375/median-of-column-with-awk
[5]:https://unix.stackexchange.com/questions/13731/is-there-a-way-to-get-the-min-max-median-and-average-of-a-list-of-numbers-in
[6]:https://kunst1080.hatenablog.com/entry/2014/02/15/180842
[8]:http://shuzo-kino.hateblo.jp/entry/2015/09/17/235436
[10]:https://qiita.com/tukiyo3/items/f75561a0cfbb7c2eedc9
[11]:https://orebibou.com/2016/07/sed%E3%81%A7%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%9C%80%E5%88%9Dor%E6%9C%80%E5%BE%8C%E3%81%AE%E8%A1%8C%E3%81%AB%E6%8C%BF%E5%85%A5%E8%BF%BD%E8%A8%98%E3%81%99%E3%82%8B/