とことんDevOps | 日本仮想化技術のDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けする「とことんDevOps」では、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果、TipsなどDevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、コンテナ開発、IaCなど
読者登録と各種SNSのフォローもよろしくお願いいたします。

CSVkitで遊ぶ

ちょっと前にこんな話が話題になりました。

内閣府が国民の休日をCSVファイルとして公開しているのですが、 旧来は

https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv

として公開していたものを特に事前告知することなく次のように変更してしまったのです。

https://www8.cao.go.jp/chosei/shukujitsu/shukujitsu.csv

syukujitsushukujitsuに変わっています。その割にディレクトリーはshukujitsuなので、今回アップデートされた名前のほうが正しいのです。

このデータをダウンロードして使っているだけであれば、前の手順でダウンロードしようとしたときに404とかが返ってきて?って思うだけなのですが、なにかのソフトウェア(例えばスケジューラーアプリ)で使っていた場合は、もともと指定していたURLが404になるのでファイル名変更によりエラーを起こしたり、最新の情報に更新されたりするなどのトラブルが起こることが考えられました。

おそらくtypoしたまま公開しているのは嫌だなと担当者が思ったのか、もしくは特にそのような意図はなく、ディレクトリ名と同じファイルで新しいデータとして登録してしまったと思われます。

色々あって、現在はもとのURLに戻っています。変えて以前のURLを廃止にするのではなくて、リダイレクトすればよかったですね。

このデータが掲載されているページはこちらで、CSVファイルもここから入手できます。

国民の祝日について - 内閣府

CSVkitとは

さて、話を戻して今回取り上げるCSVkitは、CSV形式のデータを色々CLIで弄くり回せるツール群です。

csvkit.readthedocs.io

OSによってパッケージが公開されているものもありますが、 基本的にはPythonモジュールなので、次のようにインストールできます。

% python3 -m  pip install csvkit

インストールすると、次のページで取り上げられているようなコマンドを使えるようになります。

csvkit.readthedocs.io

色々あって覚えるのは大変ですが、私はcsvlook、csvcut、csvstat、csvsqlをよく使います。

CSVkitでCSVファイルをいじくり倒してみよう

まずはCSVファイルを覗いてみましょう。中身を参照するには、csvlookコマンドが簡単で便利です。 せっかくsyukujitsu.csvの話を出したので、このファイルを例にして色々触ってみましょう。

% csvlook syukujitsu.csv 
Your file is not "utf-8-sig" encoded. Please specify the correct encoding with the -e flag or with the PYTHONIOENCODING environment variable. Use the -v flag to see the complete error.

おおっと...そうでした。このファイルはShift_JISでエンコーディングされているのでした。 というわけで、-eオプションでsjisを指定して実行します。

% csvlook -e sjis syukujitsu.csv

毎回文字コード指定が面倒であれば、nkfかiconvで文字コード変換してしまいましょう。ファイル名にハイフンをつけると後で困るので、アンダーバーにしておきましょう。なんで急にそんな事をいうかというと、自分が後で困ったからです。Windowsでは...ちょっとわからないので、WSLv2でLinux上で実行してください。Ubuntuとかならかんたんです。

% iconv -f SHIFT-JIS -t utf-8 syukujitsu.csv > syukujitsu_utf8.csv

これで文字コード指定をすることなく、CSVkitで中身を見ることができます。

% csvlook syukujitsu_utf8.csv

ただ、大容量のCSVファイルだと件数が膨大だと思いますので、--max-rowsオプションを使うことで件数を絞り込めます。 こんな感じです。結果は表形式で出てきます。

% csvlook -e sjis --max-rows 10 syukujitsu.csv 
| 国民の祝日・休日月日 | 国民の祝日・休日名称 |
| ---------- | ---------- |
| 1955-01-01 | 元日         |
| 1955-01-15 | 成人の日       |
| 1955-03-21 | 春分の日       |
| 1955-04-29 | 天皇誕生日      |
| 1955-05-03 | 憲法記念日      |
| 1955-05-05 | こどもの日      |
| 1955-09-24 | 秋分の日       |
| 1955-11-03 | 文化の日       |
| 1955-11-23 | 勤労感謝の日     |
| 1956-01-01 | 元日         |
|        ... | ...        |

CSVkitはPythonモジュールなので、次のようにPythonからデータを使って色々できるようです。

% csvpy -e sjis --agate syukujitsu.csv
Welcome! "syukujitsu.csv" has been loaded in an agate.Table object named "table".
>>> table.print_table()
| 国民の祝日・休日月日 | 国民の祝日・休日名称 |
| ---------- | ---------- |
| 1955-01-01 | 元日         |
| 1955-01-15 | 成人の日       |
| 1955-03-21 | 春分の日       |
| 1955-04-29 | 天皇誕生日      |
| 1955-05-03 | 憲法記念日      |
| 1955-05-05 | こどもの日      |
| 1955-09-24 | 秋分の日       |
| 1955-11-03 | 文化の日       |
| 1955-11-23 | 勤労感謝の日     |
| 1956-01-01 | 元日         |
| 1956-01-15 | 成人の日       |
| 1956-03-21 | 春分の日       |
| 1956-04-29 | 天皇誕生日      |
| 1956-05-03 | 憲法記念日      |
| 1956-05-05 | こどもの日      |
| 1956-09-23 | 秋分の日       |
| 1956-11-03 | 文化の日       |
| 1956-11-23 | 勤労感謝の日     |
| 1957-01-01 | 元日         |
| 1957-01-15 | 成人の日       |
|        ... | ...        |

データの概要を知るには、csvstatというコマンドを使います。 行数は1013行で、1955年1月1日から2024年11月23日のデータが含まれていることが実行結果からわかります。

% csvstat -e sjis syukujitsu.csv
  1. "国民の祝日・休日月日"

    Type of data:          Date
    Contains null values:  False
    Unique values:         1013
    Smallest value:        1955-01-01
    Largest value:         2024-11-23
    Most common values:    1955-01-01 (1x)
                           1955-01-15 (1x)
                           1955-03-21 (1x)
                           1955-04-29 (1x)
                           1955-05-03 (1x)

  2. "国民の祝日・休日名称"

    Type of data:          Text
    Contains null values:  False
    Unique values:         23
    Longest value:         12 characters
    Most common values:    休日 (110x)
                           元日 (70x)
                           成人の日 (70x)
                           春分の日 (70x)
                           憲法記念日 (70x)

csvsqlは名前の通り、CSVのデータを対応するSQLサーバーにデータを取り込んだり、CSVファイルをソースとしてSQLコマンドでCSVファイルの中身を検索できます。

内部的にSQLAlchemyモジュールを使っているため、このコマンドを利用するには別途インストールする必要があります。 ちなみにSQLAlchemy 2.0でAPI変更があったようで、このコマンドを実行すると警告が表示されるかもしれません。

docs.sqlalchemy.org

この警告は、1.4から2.0で変更されたAPIを利用している場合、コマンドを実行する毎に表示されます。回避策は.bashrcや.zshrcなどに export SQLALCHEMY_SILENCE_UBER_WARNING=1と書いておくことです。なお、うっかりPythonモジュールをSQLAlchemy 2.0以降にしてしまうと古いAPIが使われている限り動作しなくなる恐れがあるので、対応されるまで気をつける必要があります。

私は作業環境を作るときは次のようなrequirements.txtを書いておいて、セットアップしています(実際のファイルは、もっと色々書いています)。

% cat ~/requirements.txt 
pandas
pyarrow
csvkit
sqlalchemy<2.0
psycopg2-binary

% python3 -m  pip install -r requirements.txt

さて、まずは次のように実行して件数を確認してみましょう。

% csvsql -e sjis --query "SELECT COUNT(*) FROM syukujitsu" syukujitsu.csv 
COUNT(*)
1013

1013件と出ました。前にcsvstatコマンドを実行したときに現れた件数と一緒ですね。 それではデータから、成人の日を絞り込んでみましょう。

% csvsql -e sjis --query "SELECT * FROM syukujitsu WHERE 国民の祝日・休日名称='成人の日'" syukujitsu.csv 
国民の祝日・休日月日,国民の祝日・休日名称
1955-01-15,成人の日
1956-01-15,成人の日
1957-01-15,成人の日
1958-01-15,成人の日
...
1998-01-15,成人の日
1999-01-15,成人の日
2000-01-10,成人の日
2001-01-08,成人の日
...
2019-01-14,成人の日
2020-01-13,成人の日
2021-01-11,成人の日
2022-01-10,成人の日
2023-01-09,成人の日
2024-01-08,成人の日

上の例は件数の都合で省略してますが、実際はすべて出力されます。 SQLコマンドを実行しましたが、結果はCSVデータのようにカンマ区切りで表示します。データベースの結果表示出力のような表形式で出力されるcsvlookやcsvpyとは結果の出方が異なります。

ところで成人の日って昔は1月15日だったイメージを筆者は持っていたのですが、2000年以降は連休になるように調整されるようになったようですね(20年以上気がついていないという...)。翌日休みじゃないと確かに辛いですからね。

さて、csvsqlはBETWEENもきちんとサポートしていますので、期間で絞り込みなどもラクにこなせます。 例えば次のような感じで、2000年の元日からおおみそかまでを表示できます。

% csvsql -e sjis --query "SELECT * FROM syukujitsu WHERE 国民の祝日・休日月日 BETWEEN '2000-01-01' AND '2000-12-31'" syukujitsu.csv 
国民の祝日・休日月日,国民の祝日・休日名称
2000-01-01,元日
2000-01-10,成人の日
2000-02-11,建国記念の日
2000-03-20,春分の日
2000-04-29,みどりの日
2000-05-03,憲法記念日
2000-05-04,休日
2000-05-05,こどもの日
2000-07-20,海の日
2000-09-15,敬老の日
2000-09-23,秋分の日
2000-10-09,体育の日
2000-11-03,文化の日
2000-11-23,勤労感謝の日
2000-12-23,天皇誕生日

また、売上データが含まれるCSVファイルでは、集約関数をつかった集計も可能です。

% csvsql --query "SELECT name,okashi,price FROM katta_okashi" katta_okashi.csv  
name,okashi,price
Alice,candy,100.0
Alice,candy,50.0
Alice,cookie,30.0
Bob,chocolate,200.0
...
% csvsql --query "SELECT sum(price) FROM katta_okashi" katta_okashi.csv
sum(price)
3300.0

よく使うようなSQLコマンドはおおよそ使えそうです。

csvcutもよく使います

今回は詳しく取り上げませんが、csvcutなんかも便利です。データから不要な情報を取り除けます。こんなこともコマンド一発でできて超便利でした。 まずはCSVファイルに含まれるデータを覗いて...2,3,8,9行目が不要だったとします。

% csvcut -n abc6-formated.csv
  1: 60.66.22.213
  2: -
  3: -
  4: 09/Feb/2023:10:24:25 +0000
  5: GET
  6: /category/finance
  7: 200
  8: 76 "/category/garden" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML
  9:  like Gecko) Chrome/17.0.963.56 Safari/535.11"

先の判断から次のように実行すると、CSVのデータから必要ではない部分を取り除いて別のファイルとして保存できます。

% csvcut -c 1,4,5,6,7 abc6-formated.csv > abc6-formated-filtered.csv

こういう作業、以前はExcelかLibreOfficeのCalcを使っていました。マウスとキーボード操作しながらポチポチ作業をしていて、「なかなかダサいやり方をしているな」って思ったのですが、CSVkitのおかげで作業が断然ラクになりました。もう、雑にCSVファイルをExcelとかで読み込んでデータを壊すなんてこともありません(えっ?壊したことがあるのかですって?...ははは)。

編集前のと編集後のデータを比較するとこんな感じです。

$ ls -lh
-rw-rw-r-- 1 ubuntu ubuntu 398M Feb 10 14:40 abc6-formated-filtered.csv
-rw-rw-r-- 1 ubuntu ubuntu 1.2G Feb 10 13:44 abc6-formated.csv

今回CSVkitのいくつかをご紹介しました。ちなみに私のお気に入りはcsvcutとcsvsqlですね。 以上、今日はCSVkitについてのご紹介でした。