Perl Hackers Hub

第69回 表形式データを操るUNIXシェル型Perl製コマンド群 ―ビッグデータ時代の汎用的なデータ整備と分析のために(1)

この記事を読むのに必要な時間:およそ 3 分

本連載では第一線のPerlハッカーが回替わりで執筆していきます。今回のハッカーは,デジタルガレージにてデータ分析のお仕事をしている下野寿之さんで,テーマは「表形式データを操るUNIXシェル型Perl製コマンド群」です。各コマンドの実体はPerlで書かれた単体で動くプログラムで,単独または既存のコマンドと組み合わせて実行します。

本稿のサンプルコードは,WEB+DB PRESS Vol.124のサポートサイトから入手できます。

データ分析の難しさ

ビッグデータやデータサイエンスという言葉がよく使われ始めて何年も経ちますが,企業に蓄積されたPOSや人事などのデータを存分に活用するのは,まだいろいろと厳しいのが現実です。

データには不具合がある

現場のデータには,次のような不具合が多数あります注1⁠。

  • (a)不要なデータの混入注2
  • (b)仕様書未記載かつ担当者も知らない特別値注3
  • (c)データの欠損注4
  • (d)ある列で特定の値が不自然に頻発注5
  • (e)複数の表データ間での不整合注6

そのため,不具合を簡潔に調べる手段が必要です。

注1)
業務用データベースに収められたテーブル(表)は,顧客対応やコスト管理などの目的で限られた予算で作られたのであって,ほかの目的で分析する場合には,多数の不具合が通常は見つかります。
注2)
ただのゴミデータの混入も,氏名のハッシュ化し忘れもあり得ます。
注3)
データ蓄積システムのつぎはぎ製造などが原因です。
注4)
複数のデータの照合なしには発見不能な欠損や,通信エラー,台風など外部要因を知らないとわけのわからない欠損もあります。
注5)
サーバの設定変更や地震などが原因となり得ます。その影響を除去しないと,分析の結果が意味をなさないことがあります。
注6)
サーバからのエクスポートに時間がかかるとその間に情報が変わり,最初と最後の表で登録者情報の不一致などが発生し得ます。

使用するデータの選定が難しい

次の理由により,多数のデータからどの部分を本格的な分析に使うかを決める手間も,際限なく増えがちです。

  • (f)各列の(符号や略語や特別値)の意味が不明
  • (g)1枚の表が持つ列数が過多注7
  • (h)1枚の表に酷似した列が複数注8
  • (i)異なる表に同じ意味の列が過多注9
  • (j)提供された表の枚数が過多注10

そのため,分析に使う可能性のある表と列を網羅的に簡潔に調べる手段が必要です。

注7)
業務用データの表は数十列,数百列を持つことがあります。
注8)
酷似が理由でどれを使えばよいのか迷います。どの列を使ったのかのメモをきちんと残さないと,分析の再現が難しくなります。
注9)
複数の表はお互い結び付けてこそ意味があるはずですが,結び付け方の候補が多すぎると,結び付け方からして迷います。
注10)
1枚の表の理解も大変ですが,その大変さが枚数分増えます。

既存ソフトウェアの問題点

UNIX系コマンドや,Pythonライブラリのcsvkitやpandas_profiling,R言語,既存の商用ソフトウェアでは,(a)(j)の10項目への対処は困難です。(a)(e)の実務データの不具合への対処は,初歩的には可能でも全貌の把握は困難です。教科書的な基礎統計量(平均や最大,最小など)を算出するユーティリティは多いですが,ありがちな特別値-1999や空欄など)を含む符号化された文字列データを解読するには,アドホックな操作が必要で汎用性に難があります。(f)(j)の使用するデータの選定も,目的に見合った容易さではプログラミングできません。たとえば,全3列のデータに対して2列目を末尾(右端)に移動したい場合注11はAWKでawk '{print $1,$3,$2}'と書けばよいものの,12列もある場合は,12列分をコマンドラインに入力するか,for文を使ったプログラミングを行う必要があります。これらは1回ではなかなか正しく書けないため,デバッグ作業が発生して本来の分析目的に集中しづらくなり(f)⁠g)⁠h)で起こる問題の解決を遠ざけます。(i)⁠j)はなおさら難しいです。

注11)
売上データを画面で見るときに,商品名の横幅の凸凹な列を,日付や商品コードなど横幅のそろう列の右に移動させたくないですか?

表形式データを扱う新しいPerl製コマンド群

上記の現状を打破すべく,UNIXコマンドとして動くプログラムをPerlで作り,CPANモジュール化しました。本稿ではそのうちの8個を紹介します。

Perl製コマンドのメリット

(a)(j)に対処するプログラムの動作は,CPUリソースよりもデータ記憶装置からの読み取り速度が律速する場合が多いです。そのため,C++言語などを採用する必然性は低く,スクリプト言語の中で,文字列処理,多次元配列や多次元ハッシュなどの構文が書きやすいPerlを採用しました注12⁠。Perlは後方互換性が高いので,今作ったプログラムが50年後も使える見込みが高いのも理由です。

注12)
ほかには,文字の着色,シグナルの処理,10進文字列perldoc perlnumberを参照してください)の機能もPerlは使いやすいです。

インストール方法

本稿で使う各コマンドは,App::を前置したモジュール名を指定してインストールします注13⁠。

8個のコマンドを逐次インストールする場合
$ cpanm App::csv2tsv
$ cpanm App::csel
$ cpanm App::expandtab
$ cpanm App::crosstable
$ cpanm App::colsummary
$ cpanm App::venn
$ cpanm App::digitdemog
$ cpanm App::colgrep

8個のコマンドを一括インストールする場合
$ cpanm App::Bin4TSV::8

plenvを使っている場合は,各コマンドを利用可能とするため以下を実行
$ plenv rehash

これらのコマンドは,Perl 5.14(2011年5月リリース)以降で動作します注14⁠。本稿の各コマンドの動作は,Perl 5.18.4とPerl 5.32.1で確認しました。

注13)
作成したプログラムは,過去10年間更新されていない計算機環境でも動かせることを目指しました。世界中の多くの計算機で利用可能としたいためです。インストーラの働きを持つ標準モジュールExtUtils::MakeMakerすら動かない場合は,パス(PATH)の通ったディレクトリにプログラムを手動で置くことで対処してください。
注14)
各プログラムの最初のほうのステートメントはuse 5.014;です。

コマンドの共通オプション

各コマンドには,そのコマンド独自のオプションのほかに,コマンド間で共通するオプションがあります。たとえば-jは,出力を日本語表記にすることを指定するためのものです。-~は,何かの機能を反転するときに使います。

いくつかのオプションは,パラメータを持ちます。-m0など0を伴う場合の多くは,何かの機能の抑制の指定です。-iは,タブ文字以外への入力の列区切り文字の指定です。-gは,何かの抽出指定数の指定です。

各コマンドのマニュアルは,各コマンドに--helpを付与して実行すると表示されます。

扱うデータはTSV形式

多くのデータは表形式で,SQLのテーブルのように行(レコード)と列(属性)からなります注15⁠。表形式のデータはCSVComma-Separated Valuesカンマ区切り)形式が多いですが,本稿ではCSVの亜種であるTSVTab-Separated Valuesタブ区切り)形式のデータを処理します。CSV形式は引用符囲みやエスケープ文字の処理がやっかいなためです。

自由に使えるTSVファイルとしては,国立国会図書館のオープンデータセットなどがあります。

注15)
慣例上,⁠行」は横長1行の文字列で,⁠列」は縦並びの値の組です。

CSV形式からの変換 ─⁠─csv2tsv

CSV形式のファイルをTSV形式に変換する場合は,csv2tsvを使います注16⁠。

CSV形式をTSV形式に変換。リダイレクション(<)の使用が望ましい
$ csv2tsv < foo.csv > foo.tsv
注16)
文字コード変換が必要なら,別途nkfまたはiconvも使います。

自在な列操作 ─⁠─csel

便利さに長年の定評があるAWKをさらに便利にすべく,cselを作りました。たとえば,csel -p3..7により,3~7列目だけを出力します。

1行12列のTSVファイルの作成
$ perl -E'say join"\t",1..12' | tee c12.tsv
1   2   3   4   5   6   7   8   9   10  11   12

-pで指定列のみ抽出。「..」は範囲指定。AWKより簡単
$ csel -p3..7 c12.tsv
3   4   5   6   7

-dで指定列を出力抑制。「,」は連結指定。AWKだと難しい
$ csel -d3..7,10,12 c12.tsv
1   2   8   9   11

-hで指定列を先頭(左端)に移動。「12..10」は逆順指定(12,11,10)
$ csel -h12..10 c12.tsv
12  11  10  1   2   3   4   5   6   7   8   9

-tで指定列を末尾(右端)に移動。レイアウトを崩す列の移動に実用的
$ csel -t9,6 c12.tsv
1   2   3   4   5   7   8   10  11  12  9   6

列を縦ぞろえで表示 ─⁠─expandtab

表形式のデータは,lessで中身を見ても,同じ列が縦にそろわず画面で読みにくい場合があります。その場合はexpandtabを使います。expandtabは,全列が縦にそろうように,各列の最も幅の広い文字列に合わせて各値の右側に半角空白を補填します。

次の例では,Amazon Linuxのパスワードファイル/etc/passwdに対して実行しています。

-sで与えた閾値より幅広な文字列があると,その行の残りの列は狭くなる
$ expandtab -i: -c🔷 -s15 /etc/passwd

実行結果は図1です。

図1 expandtabでAmazon Linuxの/etc/passwdを縦ぞろえ

図1 expandtabでAmazon Linuxの/etc/passwdを縦ぞろえ

ファイルがタブ区切りではない場合は,-iオプションで区切り文字を指定します。ここでは/etc/passwdの区切り文字である:を指定しました。

数値パラメータ付きの-sオプションにより,できるだけ多くの列の先頭を縦にそろえつつ,出力表の横幅を縮めることができます注17⁠。

注17)
オプション-s15の指定により,図1の最後の2行のように幅(半角文字換算)が15を超過する列がある場合,超過している列の行は,該当列(図1では5列目)は幅15を超過して記載し,ほかの列で幅を狭めて記載することで超過分を吸収します。超過していない列の行は,該当列に幅15を与えます。

<続きの(2)こちら。>

WEB+DB PRESS

本誌最新号をチェック!
WEB+DB PRESS Vol.125

2021年10月23日発売
B5判/168ページ
定価1,628円
(本体1,480円+税10%)
ISBN978-4-297-12435-9

  • 特集1
    作って学ぶプログラミング言語のしくみ
    インタプリタ,構文解析器,文法
  • 特集2
    GraphQL完全ガイド
    RESTの先へ! フロントエンドに最適化されたAPI
  • 特集3
    速習DynamoDB
    AWSフルマネージドNoSQLの探求

著者プロフィール

下野寿之(しものとしゆき)

普段はデータ分析のお仕事。最近は新型コロナ関係が多め。自由度2のt分布や楕円球を使った数理モデルで数値の意味を近年考えている。16年前に量子計算で博士を取得し,7年前に医学統計学を学ぶ。

GitHub:tulamili
SlideShare:https://www.slideshare.net/shimonotoshiyuki