【シェルと遊ぼう】日本のコロナ感染者情報取得スクリプト その3(条件による行の抽出)

はじめに

こちらの記事の続き。

作るもの

今回は指定した条件でフィルタリングする処理について。

  1. データベースの作成・更新と全行全項目の出力
  2. 全感染者の指定項目を取得 (列の抽出,並べ替え)
  3. 指定項目の条件でフィルタ (行の抽出)      <== 今回はこれ
  4. 指定項目の内訳集計 (出現回数のカウント)

以降では、前回同様作るスクリプト名をcovidとした前提で説明する。

実行イメージ

使い方は以下みたいな感じ。

-mオプションの引数に条件式を渡して、条件にマッチした行だけ出力する。
条件は全体をシングルクォーテーションで囲う必要あり。

条件式は、項目名だけ列位置に置き換えてawkにそのまま渡す。
つまりawkの条件式がそのまま使えちゃう手抜きステキ仕様。

$ covid -m 'age<20' date area2 age sex            # 20代未満の感染者のみ取得
確定日,居住地,年代,性別
2020/02/18,和歌山県,10,男性
2020/02/21,北海道,0,男性
2020/02/21,北海道,10,男性
...
...
2020/05/12,大阪府,0,女性
$ covid -m 'date~"2020/05" && area2=="東京都"'    # 2020年5月の東京都の感染者のみ取得
ID,確定日,受診都道府県,居住地,居住地(詳細),年代,性別,職業,備考,状態
14453,2020/05/01,東京都,東京都,東京都,70,女性,,,
14454,2020/05/01,東京都,東京都,東京都,70,女性,,,
14455,2020/05/01,東京都,東京都,東京都,70,女性,,,
...
...
16054,2020/05/13,東京都,東京都,東京都,不明,不明,,,
$ covid -m 'sts~"無症状"'                     # 症状がない感染者のみ取得
ID,確定日,受診都道府県,居住地,居住地(詳細),年代,性別,職業,備考,状態
10,2020/01/30,千葉県,中華人民共和国,中華人民共和国,50,女性,,,退院(チャーター無症状2)
16,2020/01/31,不明,不明,不明,30,男性,,,(チャーター無症状3)
19,2020/02/01,不明,不明,不明,30,男性,,,(チャーター無症状5)
...
...
16031,2020/05/12,大阪府,大阪府,大阪府大阪市,20,男性,医療従事者,,(国内無症状)

ただ、age=10など、===にして代入しちゃうミスはやりがち。
なので=単体は==に置き換える処理を追加。

件数カウント

条件を指定できるようになったら、その条件に引っかかった件数を数えたくなるもの。
grepとかwcでいいんだけど、すぐに実装できるのでしちゃう。

$ covid -c -m 'sts~"無症状"'                  # 症状がない感染者のみ取得
658

条件に引っかかった行があればcountをインクリメントしていき、最後に出力。

集計された行数と感染者数は国や自治体の発表とだいたい合ってそう。
元データのQ&Aによると、公表日を基準とするか、PCR検査での確定日を基準とするかで差異があるとのこと。

ただ各自治体によって、集計に使うのが診断都道府県か、居住都道府県で差異がありそうなんだけど、気のせいかしら。。

条件式で使える演算子など

前述だけど、awkで使える条件式を使える。
つまり、awk A{print $0}Aの部分に使う条件式を-m 'A'で指定する。

awkのパターンで使える代表的な演算子は以下。

演算子 意味
X==Y AとBは同値。本スクリプトではA=Bでも可
X!=Y AとBは同値
X>=Y AはB以上
X<=Y AはB以下
X>Y AはBより大きい
X\<Y AはBより小さい
X~Y Aは正規表現Bにマッチする
X!~Y Aは正規表現Bにマッチしない
!A 論理反転
A&&B AかつB
A |B |AまたはB

文字列はダブルクォーテーションで囲わないといけない。

全列を対象としたgrepっぽいことをするなら-m '/検索パターン/'とすればOK。

列項目について

前回も載せたけど、指定可能な列項目は以下。

項目 パラメータ 備考 出力例
ID id データの通し番号。元データの「通し」 1/2/3...
確定日 date PCR検査の陽性確定日 '2020/05/13'など
受診都道府県 area1 陽性確定時に感染者が受診していた医療機関がある都道府県。空港名もある... 東京都/沖縄県/不明など
居住地 area2 感染者の住居都道府県または居住国 東京都/沖縄県/不明など
居住地(詳細) area3 元データ項目では「キー」。居住都道府県と同じデータも多数 東京都/沖縄県那覇市/不明など
年代 age 感染者の年代。元データは100歳以上も90に含むっぽい 0/10/20/30/40/50/60/70/80/90/100/不明
性別 sex 感染者の性別 男性/女性/不明
職業 job 感染者の職業。記載がある件数が少なく同職種でも記載に統一感なし... 会社員/看護師/無職/不明(#1)など
備考 note 詳細地区や年齢やその他補足情報っぽい 足立区/100歳以上/再陽性
状態 sts 感染者の状態。元データの「ステータス」と「無症状病原体保有者」の組み合わせ 死亡/退院/退院または死亡(国内無症状)/空欄

年代については元データをスクリプトで以下のように加工して出力する。

元データ 出力 備考
0-10 0 10歳未満
90 100 備考に「100歳以上」と記載がある場合のみ
空欄 不明

コード

行の抽出に関する主要コードは以下。

コード全体はこちらを参照されたし。

条件の初期化と設定

-mオプションで渡された引数を変数にセットしておく。
初期値は空文字。

PICKUP_COND=
# 〜 中略 〜
while getopts cf:hm:tuV OPT
do
  case $OPT in
    # 〜 中略 〜
    m ) PICKUP_COND=$OPTARG  ;;
    # 〜 中略 〜
  esac
done

列項目名から列位置への置き換え

-mオプションの引数の列名からawkの列位置への置き換えと、===への置き換えは以下。

PICKUP_COND=`echo $PICKUP_COND | $NAME_TO_POS \
  | sed 's/\([^!~><=]\)=\([^=]\)/\1==\2/g'`

$NAME_TO_POSには、出力する列項目の指定用に作った以下の関数が入ってる。

item_name_to_pos(){
  sed -e 's/id/$1/g'    \
    -e 's/date/$2/g'    \
    -e 's/area1/$3/g'   \
    -e 's/area2/$4/g'   \
    -e 's/area3/$5/g'   \
    -e 's/age/$6/g'     \
    -e 's/sex/$7/g'     \
    -e 's/job/$8/g'     \
    -e 's/note/$9/g'    \
    -e 's/sts/$10/g'
}

単純に単語を置き換えているだけ。
つまり、列名以外の条件式にパラメータ名(idとかdateとか)が出現した場合も置き換えちゃう。
(-m 'job~"job"'とした場合、$8~"$8"に置き換える)

なんてこった。
とはいえそんな条件を使うことがパッと思い浮かばないので、問題になったら対策しましょ。

ちなみに===への置き換えも同じ課題を抱える。
なんてこった。

出力

出力するコードのケースは前回までと同じ。
(ただ、本記事を執筆時にヘッダの出力処理を追加した)

$PICKUP_CONDに引数で渡した条件式が入り、あとはawkがよろしくやってくれる。
条件未指定の場合は、$PICKUP_CONDは空なので、全行出力となる。

else
  # output pickup headr
  head -n 1 "$DB_FILE"  \
    | awk -F"," '
      BEGIN{ OFS="," }
      {
        print '"${OUTPUT_ITEMS}"'
      }'

  # output pickup recodes
  cat "$DB_FILE"    \
    | sed '1d'      \
    | awk -F"," '
      BEGIN{ OFS="," }
      '"$PICKUP_COND"'{
        print '"${OUTPUT_ITEMS}"'
      }'
fi

awkすごい。

カウント

行数をカウントして出力しているのは以下。
-cオプションが指定された場合に通るケース。
sedで1行消しているのはヘッダ行。

elif [ -n "$CNT_OF_RECODE" ]; then
  # output count of pickup recodes
  cat "$DB_FILE"    \
    | sed '1d'      \
    | awk -F"," '
      BEGIN{ count=0 }
      '"$PICKUP_COND"'{
        count++
      }
      END{ print count }'
else

さいごに

行の抽出もawkのおかげでサクサク。

これを正規表現でやろうとすると超絶ウルトラスーパーツラい。

ありがとうawk
ありがとうawkを作った皆さま。

次回は、列項目内の内訳集計処理。

確認環境

PC Thinkpad X1 Carbon 2nd Gen
OS FreeBSD 12.1-RELEASE-p4

参考