要約
データを1列に並べる.ソートする.それぞれのデータを複製して2つずつにする.rs
で2列にする.1列目の最後データと2列目の頭のデータとの平均が中央値である.
% 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
コマンドで練習用のデータを準備する.
% jot -r 3
54
4
34
(2)データを1列に並べる.
- jotコマンドでは勝手に1列に並ぶので省略.
- 通常は
tr
やsed
で区切り文字を改行に置換して1列に並べる.
echo '1 2 3' | tr ' ' '\n'
1
2
3
(3) 数を昇順に並べる.
-
sort -n
を使う. - 例えば"11"を"1"と"1"との文字列ではなく,数値の"11"として読むのに,
-n
が必要.
% 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つ並ばせる.
% echo '1 2' | sed 'p'
1 2
1 2
% echo '1 2' | sed -n 'p'
1 2
(5) 縦方向で2列にする.
-
rs
を用いる. -
-t
で縦方向にする. -
-C
で区切り文字を指定しない場合,スペース -
r 2
とすると,行指定なし,2列
となる.
% 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列目の最初の数値の平均で中央値が求まる.
%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列目最初の数の平均で中央値を求めえることができる.
- すなわち,"要素数を数える作業"と"偶奇の場合分け"とを回避できた.
%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
ブロックで計算. - 以降は見通しを良くするため適宜改行する.
% 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つの数を取り出す.
# 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].
% 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位まで表示する.
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:<<()を用いる
% 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:ヒアストリング<<<
を用いる
~~略~~
| <<< '+ 1k 2 / p' | dc
その3:sedを用いる
- 最終行の末尾に文字列を追加する.
- sedの区切り文字
/
と割り算の/
が同じでエラーがでる.前者を別の文字にするか,後者をバックスラッシュ\
でエスケープする.
~~略~~
| 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)
~~略~~
| sed '$a \'$'\n + 1k 2 / p' | dc
# 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最後の行に挿入(追記)する | 俺的備忘録 〜なんかいろいろ〜