1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ShellScript: rs(reshape)コマンドを利用し,場合分けなしで(ifを使わず)中央値を求める.

Last updated at Posted at 2020-05-17

要約

 データを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で乱数を作成している.
% jot -r 3 
54
4
34

(2)データを1列に並べる.

  • jotコマンドでは勝手に1列に並ぶので省略.
  • 通常はtrsedで区切り文字を改行に置換して1列に並べる.
置換を利用して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'を使う.
  • sedpコマンドは "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で区切り文字を指定しない場合,スペース 2個が区切り文字となる.
  • 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を用いた方法.

  • これが一番素直でなかろうか.
  • 変数n1n2にそれぞれの数を代入し,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位まで表示する.
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:<<()を用いる

% 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最後の行に挿入(追記)する | 俺的備忘録 〜なんかいろいろ〜

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?