【Python】ClickでCSVを集計するCLIツールを作ってみた

【Python】ClickでCSVを集計するCLIツールを作ってみた

公開: 更新:
CodeCampが提供するDX人材育成が可能なプログラミングやITが学べる公開講座

CSVファイルの集計を、表計算ソフトを開かずにコマンド一発で終わらせたい——そんな場面は意外と多いのではないでしょうか。今回はPythonClickと標準ライブラリのcsvだけで、売上CSVを集計する小さなコマンドラインツールcsvaggを作ってみました。

samplesummarygroupbyfilterの4コマンドを備え、数値列の自動判定から集計、絞り込み、CSV出力までをこなせます。初心者でも追体験できるように、実装の要点と実行の様子を順番に解説していきます。

Clickの基本概念、要件定義、実装、動作確認までを順番に学べる構成です。動画は目次から確認したい場面へ移動でき、本文だけでも手順と考え方が完結します。

【Python】ClickでCSVを集計するCLIツールを作ってみた|フルカリキュラム|概要・完成挙動
動画の内容をテキストで確認する

オープニング。Clickと標準csvを使ってCSV集計CLIツールを作るカリキュラムを始めます。概要紹介。

Clickと標準csvの役割と使い方を学ぶCSV集計CLIツールの要件を整理する 完成コードと実行結果を確認する 最後に実コマンドとファイル状態で完成挙動を確かめる 具体的にやること。

サンプル売上CSVを書き出す 数値列を自動判定して集計する 件数・合計・平均・最小・最大を求める 指定した列でグループ化して集計する 指定した列と値で行を絞り込む 実装環境・必須アプリ。

OS:Windows 11 Pro Python:3.13.3シェル:PowerShell 5.1必須アプリ:コードエディター、ターミナル、エクスプローラー パッケージ:click==8.1.7 PythonのClickと標準csvモジュールとは。

PythonでCLIを組み立てるライブラリ「Click」の仕組み:関数をデコレータでサブコマンドに変えるCSVを読み書きする標準ライブラリ「csv」の役割:DictReaderで行を辞書として読む PythonのClickでCSV集計CLIを実装する重要ポイント。

全行が数値の列だけを対象にするsumやmeanを1つの関数で切り替えるOrderedDictで列の順序を保つ CSV集計CLIツールの要件定義。

sampleで12件の売上CSVが作られるsummaryで数量と金額が集計されるgroupbyでカテゴリ別の合計が出るfilterで一致件数と該当行が表示される 出力指定時に結果CSVが保存される 確認1/4: サンプルの売上CSVを書き出す。sampleコマンドを実行して、集計の題材になる12件の売上CSVを作成し、中身の先頭部分まで確認します。

実行2/4: 数値列を集計して表で見る。summaryコマンドで数量と金額の統計量を求め、件数や合計、平均などを表形式で表示します。確認2/4: 数値列を集計して表で見る。

summaryコマンドで数量と金額の統計量を求め、件数や合計、平均などを表形式で表示します。実行3/4: カテゴリ別に金額を合計する。groupbyコマンドでカテゴリごとに金額を合計し、カテゴリ別の売上を一覧します。

確認3/4: カテゴリ別に金額を合計する。groupbyコマンドでカテゴリごとに金額を合計し、カテゴリ別の売上を一覧します。実行4/4: 条件に合う行だけを絞り込む。

filterコマンドでカテゴリが家電の行だけを抽出し、一致件数と該当行を表示します。確認4/4: 条件に合う行だけを絞り込む。filterコマンドでカテゴリが家電の行だけを抽出し、一致件数と該当行を表示します。

学習内容のまとめ。関数をデコレータでサブコマンドに変えるDictReaderで行を辞書として読む 全行が数値の列だけを対象にする 追加ライブラリはClickだけで完結する 小さく実行確認しながら完成状態まで段階的に組み立てる エンディング。Python研修はCodeCampでご確認ください。

PythonのClickと標準csvモジュールとは

今回使用する主要なライブラリについて、役割と使い分けを順番に確認します。

CLIを組み立てるライブラリ「Click」の仕組み

Clickは、普通のPython関数をコマンドライン用のサブコマンドへ変えてくれるライブラリです。関数の上に@click.commandや@click.optionといったデコレータを重ねるだけで、引数の解釈やヘルプ表示、型チェックまで任せられます。

今回のcsvaggでは@click.groupで4つのコマンドを束ね、pathやoptionを受け取る入口として活用しました。細かな引数処理を自前で書かずに済むので、集計そのものへ集中できます。

  • 関数をデコレータでサブコマンドに変える
  • @click.groupで複数コマンドを束ねる
  • @click.argumentで必須の引数を受け取る
  • @click.optionで任意のオプションを足す
  • ヘルプや使い方を自動で生成する

CSVを読み書きする標準ライブラリ「csv」の役割

csvは、CSVファイルの読み書きを担うPython標準ライブラリで、追加インストールなしに使えます。なかでもDictReaderは各行をヘッダー名をキーにした辞書として返すため、列名で値を取り出せて便利です。書き出しにはDictWriterを用い、ヘッダー行と本体をまとめて出力します。

文字コードにutf-8-sigを選んでいる点も大切で、日本語を含むCSVをExcelで開いても文字化けを避けられます。

  • DictReaderで行を辞書として読む
  • DictWriterで辞書をCSVに書く
  • fieldnamesでヘッダーを取得する
  • utf-8-sigで文字化けを防ぐ
さらに詳しく学べるPython研修の詳細はこちら

Python・Clickで開発する場合の環境構築

この記事のセットアップ手順と掲載コードは、Windows 11 Pro、PowerShell 5.1、Python 3.13.3で動作確認しています。仮想環境を有効化せず、その中のPythonを直接指定するため、以下のコマンドはPowerShellとコマンドプロンプト(cmd)の両方で使えます。

python -m venv .venv
.\.venv\Scripts\python.exe -m pip install click==8.1.7

macOS・Linuxでは仮想環境内のPythonパスが異なります。今回の動作確認環境とは異なるため、以下は環境差分を補う参考手順です。

python3 -m venv .venv
./.venv/bin/python -m pip install click==8.1.7
  • CSVはutf-8-sig(BOM付きUTF-8)で読み書きするため、Excelで開いても日本語が文字化けしません。
  • サブコマンド型のツールです。--helpを付けて実行すると、使えるコマンドの一覧を確認できます。
さらに詳しく学べるPython研修の詳細はこちら

PythonのClickと標準csvで作るCSV集計CLIツールの要件定義

目的は、PythonのClickと標準csvライブラリで売上CSVを集計するCLIツールを作れるようになることです。

対象者として、PythonでコマンドラインツールとCSV処理の基礎を学びたい人を想定しています。

完成物は、sample・summary・groupby・filterの4コマンドを備えたCSV集計CLIツールcsvaggです。

実装へ入る前に、機能・品質・受け入れ条件を分けて確認します。

機能要件

  • サンプル売上CSVを書き出す
  • 数値列を自動判定して集計する
  • 件数・合計・平均・最小・最大を求める
  • 指定した列でグループ化して集計する
  • 指定した列と値で行を絞り込む
  • 集計や絞り込みの結果をCSVへ保存する

非機能要件

  • 追加ライブラリはClickだけで完結する
  • utf-8-sigで日本語の文字化けを防ぐ
  • 不正な列名は分かりやすいエラーで伝える
  • 固定データで実行例と手元の出力を一致させる

実装方針

今回はClickと標準csvの基本動作を追いやすくするため、CSV集計CLIツール本体を1つのPythonファイルへまとめます。

入力、判定、結果表示の役割を分け、実行結果を確認しながら機能を積み上げます。

  • Clickと標準csvのコマンド定義と処理関数の責務を分ける
  • 入力フォルダやオプションを検証してからファイル処理を始める
  • 時間のかかる処理は進捗を表示し、結果を確認できるようにする
  • 削除ではなく退避を選び、誤判定から戻せる設計にする

完成と判断する条件

  1. sampleで12件の売上CSVが作られる
  2. summaryで数量と金額が集計される
  3. groupbyでカテゴリ別の合計が出る
  4. filterで一致件数と該当行が表示される
  5. 出力指定時に結果CSVが保存される
さらに詳しく学べるPython研修の詳細はこちら

PythonのClickでCSV集計CLIを実装する重要ポイント

csvaggの心臓部は、数値列の自動判定と集計処理のまとまりです。numeric_columnsは各列の値をすべてfloatへ変換できるか試し、成功した列だけを集計対象として拾い上げます。実際の集計はaggregate関数へ集約し、合計や平均などをhowという引数で切り替えられるようにしました。

列の並び順はOrderedDictで保ち、整数はfmtで小数点を落として読みやすく表示します。

  • 全行が数値の列だけを対象にする
  • sumやmeanを1つの関数で切り替える
  • OrderedDictで列の順序を保つ
  • 整数は小数点を省いて表示する

集計する列を自動で見分ける方法

numeric_columnsは、列ごとに空でない値を集め、すべてfloatへ変換できるかを試します。1つでも変換に失敗した列は集計対象から外れるため、列名を決め打ちしなくても数値列だけを選び出せます。

  • 列ごとに全値のfloat変換を試す
  • 変換に失敗した列は対象から外す
  • 列名を決め打ちせず柔軟に選ぶ

集計方法を1か所へまとめる工夫

合計や平均といった計算は、aggregate関数へ集約しています。howという引数で処理を切り替えられるので、summaryもgroupbyも同じ関数を呼ぶだけで済むのです。未知の方法が渡されたときには、ClickExceptionで知らせます。

  • count・sum・mean・min・maxを共通化
  • howで集計方法を切り替える
  • 未知の方法は例外で知らせる

グループ化と絞り込みの考え方

groupbyでは、setdefaultを使ってグループごとに数値を集め、まとめて集計します。filterのほうは、指定した列の値が一致する行だけを残す仕組みです。どちらも画面への表示に加えて、出力指定でCSVへ保存できるようにしています。

  • setdefaultでグループごとに値を集める
  • 指定列と値が一致する行を残す
  • 表示と保存の両方に対応する

Python研修一覧はこちら

目的に合うPython研修を一覧形式から探したい方は、ぜひご利用ください。

Python研修を比較する

Java研修一覧はこちら

目的に合うJava研修を一覧形式から探したい方は、ぜひご利用ください。

Java研修を比較する

PHP研修一覧はこちら

目的に合うPHP研修を一覧形式から探したい方は、ぜひご利用ください。

PHP研修を比較する

新入社員研修

目的に合う新入社員研修を一覧形式から探したい方は、ぜひご利用ください。

新入社員研修を比較する

全ての研修からも探したい方はこちら

PythonのClickと標準csvで作るCSV集計CLIツールの完成コード

00:00 / 18:20
【Python】ClickでCSVを集計するCLIツールを作ってみた|フルカリキュラム|コード実装
動画の内容をテキストで確認する

INTRO: Clickを使ったCLI開発を開始。コードを1行ずつ入力し、節目ごとに実行結果を確かめながら完成させます。LINE 001: モジュール説明の書き出し。

このツール全体を紹介するモジュールの説明文(docstring)の1行目です。ClickというライブラリでCSVを集計する小さなCLIツールだと、ファイルを開いた人にまず伝えています。LINE 003: サブコマンド一覧の見出し。

これから用意する複数のサブコマンドを紹介する見出しの行です。この下に各コマンドの名前と役割を並べて、ツールでできることの全体像を示します。LINE 004: sampleコマンドの紹介。

sampleコマンドの説明で、デモ用の売上CSVを書き出す役割を持つことを伝えています。まず動作を試すためのデータを手元に作るためのコマンドです。LINE 005: summaryコマンドの紹介。

summaryコマンドの説明で、数値の列を自動で見分けて統計量を集計する役割を示しています。列名を指定しなくても集計できる点が特徴です。LINE 006: groupbyコマンドの紹介。

groupbyコマンドの説明で、指定した列でグループ化してから数値列を集計する役割を伝えています。カテゴリごとの合計などを求めたいときに使います。LINE 007: filterコマンドの紹介。

filterコマンドの説明で、指定した列の値に一致する行だけを絞り込む役割を示しています。必要なデータだけを取り出したいときに使うコマンドです。LINE 008: モジュール説明の終わり。

モジュールの説明文を閉じる3連ダブルクォートです。ここまでがファイル冒頭の紹介で、この後から実際のプログラムのコードが始まります。LINE 010: csvモジュールの読み込み。

標準ライブラリのcsvモジュールを読み込みます。この後で行うCSVファイルの読み書きに必要な機能を、ここで使えるようにしています。LINE 011: OrderedDictの読み込み。

collectionsから、要素の順序を保つ辞書であるOrderedDictを取り込みます。集計結果の項目を、書いた順番どおりに並べて保持するために使います。LINE 013: Clickの読み込み。

コマンドラインツールを作るための外部ライブラリClickを読み込みます。サブコマンドやオプションの定義を、この後で手軽に書けるようにしています。LINE 016: デモデータ節の始まり。

ここからデモ用のサンプルデータを用意する部分だと示すコメントです。ツールを試すための売上データを、次の行以降でプログラム内に直接書いていきます。LINE 017: 固定データにする狙い。

サンプルをあえて固定データにする理由を説明したコメントです。毎回同じ内容を書き出すので、記事の実行例と手元の出力がずれない、という狙いを伝えています。LINE 018: サンプルの列見出し定義。

サンプルCSVの列見出しをリストとして定義しています。日付・カテゴリ・商品名・数量・金額の5つが、書き出すデータの項目名になります。LINE 019: サンプル行リストの開始。

サンプルの各データ行をまとめるリストを作り始める行です。この後に売上データを1件ずつ並べ、最後の角かっこで閉じる形になります。LINE 020: 1件目の売上データ。

サンプルの1件目で、1月5日に家電の電気ケトルが2個・7600円売れたことを表します。各値はSAMPLE_HEADERの列と同じ順番で並べています。LINE 021: 2件目の売上データ。

2件目のデータで、1月8日に食品のオーガニック珈琲が5個・4500円売れた記録です。家電とは別のカテゴリを加えて、集計しがいのある内容にしています。LINE 022: 3件目の売上データ。

3件目のデータで、1月11日に家電のヘッドホンが1個・12800円売れたことを表します。同じ家電でも商品や金額が異なる行を用意しています。LINE 023: 4件目の売上データ。

4件目のデータで、1月15日に文具の万年筆が3個・9600円売れた記録です。ここで3つ目のカテゴリである文具が登場します。LINE 024: 5件目の売上データ。

5件目のデータで、1月19日に食品の紅茶ギフトが4個・6400円売れたことを表します。食品カテゴリに別の商品を追加しています。LINE 025: 6件目の売上データ。

6件目のデータで、1月22日に家電の加湿器が2個・11800円売れた記録です。家電カテゴリの品ぞろえをさらに増やしています。LINE 026: 7件目の売上データ。

7件目のデータで、1月26日に文具のノート5冊組が6個・3000円売れたことを表します。数量が多めで単価が低い行になっています。LINE 027: 8件目の売上データ。

8件目のデータで、2月2日に食品のオーガニック珈琲が3個・2700円売れた記録です。ここから月が2月に変わり、同じ商品が再び登場します。LINE 028: 9件目の売上データ。

9件目のデータで、2月6日に家電の電気ケトルが1個・3800円売れたことを表します。1件目と同じ商品を、別の日付でもう一度登場させています。LINE 029: 10件目の売上データ。

10件目のデータで、2月9日に文具のボールペンが10個・2000円売れた記録です。数量が最も多く、件数や合計の集計を確かめやすい行です。LINE 030: 11件目の売上データ。

11件目のデータで、2月14日に食品の紅茶ギフトが2個・3200円売れたことを表します。既出の商品を別の日付でも加えています。LINE 031: 12件目の売上データ。

最後となる12件目のデータで、2月18日に家電のヘッドホンが2個・25600円売れた記録です。金額が最も大きく、最大値の集計を試すのに向いています。LINE 032: サンプル行リストの終了。

サンプルデータのリストを閉じる角かっこです。これで全12件の売上データがSAMPLE_ROWSにまとまりました。RUN 1/5: サンプルデータの中身を確かめる。

固定の売上データが12件そろっているか、ヘッダーと件数を表示して確認します。CHECK 1/5: 途中実行に成功。ヘッダー: ['日付', 'カテゴリ', '商品名', '数量', '金額']件数: 12 LINE 035: CSV入出力節の始まり。

ここからCSVファイルを読み書きする関数をまとめる部分だと示すコメントです。ファイルの入力と出力を担う処理を、この節でまとめて定義していきます。LINE 036: 文字コードの方針。

読み込みも書き出しも文字コードにutf-8-sigを使う理由を説明したコメントです。Excelで開いても日本語が文字化けしないようにする工夫を伝えています。LINE 037: 読み込み関数の定義。

CSVファイルを読み込むための関数load_csvを定義しています。引数pathには、読み込みたいファイルの場所を受け取ります。LINE 038: 読み込み関数の説明。

load_csv関数が何をするかを説明するdocs

ここからはcsvaggの中身を見ていきます。ファイルは大きく分けて、デモデータの定義、CSVの入出力、数値列の判定、集計処理、コマンド定義の5つで構成しています。

集計のロジックはaggregateという1つの関数にまとめました。summarygroupbyの両方から呼び出すので、同じ計算を二重に書かずに済みます。

以下では、初心者がまず押さえたい要点だけを抜き出して順番に解説します。

このセクションの用語

DictReader
csvモジュールの読み込み機能の1つで、CSVの各行を見出し名をキーにした辞書に変換してくれます。
デコレータ
関数の前に@付きで書き、その関数に機能を追加する仕組みです。Clickでは@click.commandでコマンド化します。
例外
プログラム実行中に起きた想定外の事態を表す仕組みで、raiseで発生させ、メッセージとともに処理を止められます。
csvagg内部でデータが処理される流れ load_csvでCSVを辞書のリストに読むnumeric_columnsで数値列を判定するaggregateでsum/meanなどを計算するfmtで表示を整えて出力する
csvagg内部でデータが処理される流れ
"""csvagg: ClickでCSVを集計する小さなCLIツール。

サブコマンド:
  sample   デモ用の売上CSVを書き出す
  summary  数値列を自動判定して統計量を集計する
  groupby  指定した列でグループ化して数値列を集計する
  filter   指定した列の値で行を絞り込む
"""

import csv
from collections import OrderedDict

import click


# デモデータの定義
# 固定の売上データ。毎回同じ内容を書き出すので、記事の実行例と手元の出力が一致する。
SAMPLE_HEADER = ["日付", "カテゴリ", "商品名", "数量", "金額"]
SAMPLE_ROWS = [
    ["2024-01-05", "家電", "電気ケトル", 2, 7600],
    ["2024-01-08", "食品", "オーガニック珈琲", 5, 4500],
    ["2024-01-11", "家電", "ヘッドホン", 1, 12800],
    ["2024-01-15", "文具", "万年筆", 3, 9600],
    ["2024-01-19", "食品", "紅茶ギフト", 4, 6400],
    ["2024-01-22", "家電", "加湿器", 2, 11800],
    ["2024-01-26", "文具", "ノート5冊組", 6, 3000],
    ["2024-02-02", "食品", "オーガニック珈琲", 3, 2700],
    ["2024-02-06", "家電", "電気ケトル", 1, 3800],
    ["2024-02-09", "文具", "ボールペン", 10, 2000],
    ["2024-02-14", "食品", "紅茶ギフト", 2, 3200],
    ["2024-02-18", "家電", "ヘッドホン", 2, 25600],
]


# CSVの入出力
# 読み書きともに utf-8-sig を使い、Excelで開いても日本語が文字化けしないようにする。
def load_csv(path):
    """CSVを読み込み、ヘッダーと行(dictのリスト)を返す。"""
    with open(path, newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        rows = list(reader)
    return reader.fieldnames or [], rows


def save_csv(path, header, rows):
    """dictのリストを utf-8-sig のCSVとして書き出す。"""
    with open(path, "w", newline="", encoding="utf-8-sig") as f:
        writer = csv.DictWriter(f, fieldnames=header)
        writer.writeheader()
        writer.writerows(rows)


# 数値列の自動判定
# 列名を決め打ちせず、全ての値が数値に変換できる列だけを集計対象とみなす。
def numeric_columns(fieldnames, rows):
    """全行が数値に変換できる列名のリストを返す。"""
    numeric = []
    for col in fieldnames:
        values = [r[col] for r in rows if r.get(col, "") != ""]
        if not values:
            continue
        try:
            for v in values:
                float(v)
        except ValueError:
            continue
        numeric.append(col)
    return numeric


# 集計の共通処理
# sum / mean / count / min / max を1か所で計算し、各コマンドから使い回す。
def aggregate(values, how):
    """数値のリストに集計方法 how を適用した結果を返す。"""
    if how == "count":
        return len(values)
    if how == "sum":
        return sum(values)
    if how == "mean":
        return round(sum(values) / len(values), 2) if values else 0
    if how == "min":
        return min(values)
    if how == "max":
        return max(values)
    raise click.ClickException(f"未知の集計方法です: {how}")


def fmt(x):
    """整数とみなせる数値は小数点を省いて表示する。"""
    if isinstance(x, float) and x.is_integer():
        return str(int(x))
    return str(x)


# コマンド定義
@click.group()
def cli():
    """CSVを集計する小さなコマンドラインツール。"""


@cli.command()
@click.argument("path", type=click.Path())
def sample(path):
    """デモ用の売上CSVを PATH に書き出す。"""
    rows = [dict(zip(SAMPLE_HEADER, r)) for r in SAMPLE_ROWS]
    save_csv(path, SAMPLE_HEADER, rows)
    click.echo(f"サンプルCSVを書き出しました: {path}({len(rows)}件)")


@cli.command()
@click.argument("path", type=click.Path(exists=True))
@click.option("--output", "-o", type=click.Path(), default=None,
              help="集計結果をCSVに保存する")
def summary(path, output):
    """数値列を自動判定し、件数・合計・平均・最小・最大を集計する。"""
    fieldnames, rows = load_csv(path)
    cols = numeric_columns(fieldnames, rows)
    if not cols:
        raise click.ClickException("数値列が見つかりませんでした。")

    header = ["列", "件数", "合計", "平均", "最小", "最大"]
    result = []
    for col in cols:
        values = [float(r[col]) for r in rows if r.get(col, "") != ""]
        result.append(OrderedDict([
            ("列", col),
            ("件数", aggregate(values, "count")),
            ("合計", aggregate(values, "sum")),
            ("平均", aggregate(values, "mean")),
            ("最小", aggregate(values, "min")),
            ("最大", aggregate(values, "max")),
        ]))

    # 表として表示
    click.echo(" | ".join(header))
    for row in result:
        click.echo(" | ".join(fmt(row[h]) for h in header))

    # 任意でCSVへ保存
    if output:
        save_csv(output, header, [{k: fmt(v) for k, v in row.items()} for row in result])
        click.echo(f"集計結果を保存しました: {output}")


@cli.command()
@click.argument("path", type=click.Path(exists=True))
@click.option("--by", required=True, help="グループ化する列名")
@click.option("--target", required=True, help="集計する数値列名")
@click.option("--agg", "how",
              type=click.Choice(["sum", "mean", "count", "min", "max"]),
              default="sum", help="集計方法")
@click.option("--output", "-o", type=click.Path(), default=None,
              help="集計結果をCSVに保存する")
def groupby(path, by, target, how, output):
    """BY 列でグループ化し、TARGET 列を集計する。"""
    fieldnames, rows = load_csv(path)
    if by not in fieldnames:
        raise click.ClickException(f"列が見つかりません: {by}")
    if target not in fieldnames:
        raise click.ClickException(f"列が見つかりません: {target}")

    # グループごとに数値を集める
    groups = OrderedDict()
    for r in rows:
        val = r.get(target, "")
        if val == "":
            continue
        groups.setdefault(r[by], []).append(float(val))

    # グループ単位で集計して表示
    header = [by, how]
    result = []
    click.echo(" | ".join(header))
    for key, values in groups.items():
        agg_value = fmt(aggregate(values, how))
        result.append({by: key, how: agg_value})
        click.echo(f"{key} | {agg_value}")

    # 任意でCSVへ保存
    if output:
        save_csv(output, header, result)
        click.echo(f"集計結果を保存しました: {output}")


@cli.command(name="filter")
@click.argument("path", type=click.Path(exists=True))
@click.option("--column", "-c", required=True, help="絞り込む列名")
@click.option("--value", "-v", required=True, help="一致させる値")
@click.option("--output", "-o", type=click.Path(), default=None,
              help="絞り込み結果をCSVに保存する")
def filter_rows(path, column, value, output):
    """COLUMN 列が VALUE と一致する行だけを残す。"""
    fieldnames, rows = load_csv(path)
    if column not in fieldnames:
        raise click.ClickException(f"列が見つかりません: {column}")

    # 一致する行を抽出して表示
    matched = [r for r in rows if r.get(column, "") == value]
    click.echo(f"{len(matched)}件が一致しました(全{len(rows)}件中)")
    click.echo(" | ".join(fieldnames))
    for r in matched:
        click.echo(" | ".join(r.get(c, "") for c in fieldnames))

    # 任意でCSVへ保存
    if output:
        save_csv(output, fieldnames, matched)
        click.echo(f"絞り込み結果を保存しました: {output}")


def main():
    cli()


if __name__ == "__main__":
    main()

コード全文は上の折り畳みに入れてあるので、全部を上から読む必要はありません。ここでは特に重要な部分だけを抜き出して、何をしているのか順番に見ていきます。

utf-8-sigでCSVを読み込むload_csv

def load_csv(path):
    """CSVを読み込み、ヘッダーと行(dictのリスト)を返す。"""
    with open(path, newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        rows = list(reader)
    return reader.fieldnames or [], rows

csv.DictReaderを使い、各行を見出し名をキーにした辞書として読み込みます。encodingutf-8-sigを指定しているので、Excelが書き出したBOM付きCSVでも先頭の列名が壊れません。

全値がfloatに変換できる列を選ぶnumeric_columns

    for col in fieldnames:
        values = [r[col] for r in rows if r.get(col, "") != ""]
        if not values:
            continue
        try:
            for v in values:
                float(v)
        except ValueError:
            continue
        numeric.append(col)

列ごとに空でない値をすべてfloatに変換してみて、1つでも失敗すればその列を数値列から外します。列名を決め打ちしないので、金額でもpriceでも中身が数字なら自動で集計対象になります。

5種類の集計を1か所にまとめたaggregate

def aggregate(values, how):
    """数値のリストに集計方法 how を適用した結果を返す。"""
    if how == "count":
        return len(values)
    if how == "sum":
        return sum(values)
    if how == "mean":
        return round(sum(values) / len(values), 2) if values else 0

howという文字列で集計方法を切り替え、countsummeanなどを1つの関数で処理します。summarygroupbyの両方がこの関数を呼ぶため、集計ロジックの重複がありません。

未知の集計方法を弾くClickException

    if how == "min":
        return min(values)
    if how == "max":
        return max(values)
    raise click.ClickException(f"未知の集計方法です: {how}")

どの分岐にも当てはまらないhowが渡されたら、click.ClickExceptionで分かりやすいエラーを出します。Clickの例外を使うと、長いスタックトレースを見せずにメッセージだけを表示できます。

整数っぽい数値をきれいに見せるfmt

def fmt(x):
    """整数とみなせる数値は小数点を省いて表示する。"""
    if isinstance(x, float) and x.is_integer():
        return str(int(x))
    return str(x)

7600.0のように小数部が0の値は、intに直してから文字列にします。合計金額が7600と表示されるので、余計な小数点がつかず読みやすくなります。

@click.groupでコマンドをまとめるcli

@click.group()
def cli():
    """CSVを集計する小さなコマンドラインツール。"""

@click.group()を付けたcliが、4つのサブコマンドをぶら下げる親になります。この下に@cli.command()を付けた関数を並べるだけで、summarygroupbyが使えるようになります。

参考:

©Python公式ドキュメントcsvモジュール

If newline='' is not specified, newlines embedded inside quoted fields will not be interpreted correctly


Python基礎・実践(Django)

企業・法人向けのPython研修では、基礎から応用まで体系的に学べます。

Python研修の詳細

DX社員研修

企業・法人向けのDX研修では、実務に繋がるリスキリングでITレベルを向上させます。

DX研修の詳細

Javaエンジニア育成研修

企業・法人向けのJavaエンジニア育成研修では、Javaの基礎から応用まで確実に習得できます。

Java研修の詳細

新卒・新入社員向け研修

企業・法人に新入社員・新卒社員に向けたプログラミング研修を提供しています。

新入社員研修の詳細

コードキャンプのIT研修を全て見る

Python製CLIを実行して4コマンドを検証

実際にCLIツールを5回のコマンドで実行し、いずれも正常に(終了コード0で)動作しました。まずはsampleコマンドでデモ用の売上CSVsales.csvを書き出すところから始めます。

続いてsummary sales.csvを実行すると、数値列と判定された数量金額について合計や平均などの統計量が表示されました。列名を指定しなくても数値列を拾えているのがポイントです。

groupby sales.csv --by カテゴリ --target 金額 --agg sumでは、カテゴリごとに金額を合計した表が得られました。filter sales.csv --column カテゴリ --value 家電では家電の行だけが抜き出されます。

最後にgroupby--output by_category.csvを付け、集計結果をCSVファイルとして保存する実行も試しました。5回すべてが終了コード0で完了しています。

このセクションの用語

終了コード
プログラムが終了するときに返す番号で、0は正常終了、0以外は何らかの異常を表す慣習です。
オプション
--by--aggのように、コマンドの動作を細かく指定するための引数のことです。
python subject.py sample sales.csvの実行結果(終了コード0)
python subject.py sample sales.csvの実行結果(終了コード0)
python subject.py summary sales.csvの実行結果(終了コード0)
python subject.py summary sales.csvの実行結果(終了コード0)
python subject.py groupby sales.csv --byカテゴリ --target金額 --agg sumの実行結果(終了コード0)
python subject.py groupby sales.csv --byカテゴリ --target金額 --agg sumの実行結果(終了コード0)
python subject.py filter sales.csv --columnカテゴリ --value家電の実行結果(終了コード0)
python subject.py filter sales.csv --columnカテゴリ --value家電の実行結果(終了コード0)
python subject.py groupby sales.csv --byカテゴリ --target金額 --agg sum --output by_category.csvの実行結果(終了コード0)
python subject.py groupby sales.csv --byカテゴリ --target金額 --agg sum --output by_category.csvの実行結果(終了コード0)
さらに詳しく学べるPython研修の詳細はこちら

PythonのClickと標準csv利用時に起きやすいエラーと対処法

ここではcsvaggのようなCLIを動かすときに、初心者が一般に遭遇しやすいエラーをまとめます。特定の実行で必ず起きるものではなく、環境や入力次第で出やすい代表例です。

このセクションの用語

モジュール
Pythonの機能をまとめたファイルや部品のことです。importで読み込んで使います。
文字コード
文字をコンピュータ上で数値に対応させる方式のことで、UTF-8などいくつかの種類があります。
エラー例 原因 対処
ModuleNotFoundError: No module named 'click' Clickがインストールされていない pip install clickで導入する
FileNotFoundError: 'sales.csv' 指定したCSVのパスが違う、または未作成 先にsampleで生成するか正しいパスを渡す
UnicodeDecodeError CSVの文字コードがutf-8-sig以外になっている 元ファイルをUTF-8で保存し直す
KeyError: 'カテゴリ' --by--columnに無い列名を指定した ヘッダーと同じ列名を正確に渡す
未知の集計方法ですというClickException --aggにsum等以外の値を指定した sum/mean/count/min/maxから選ぶ

CSV集計CLIツールで注意したい点

動くコードを書けても、CSVやClickまわりには初心者がハマりやすい落とし穴がいくつかあります。事前に知っておくと、原因不明のエラーで悩む時間を減らせます。

さらに詳しく学べるPython研修の詳細はこちら

PythonのClickと標準csvで作るCSV集計CLIツールの活用例

csvaggは小さなツールですが、日々のちょっとした集計を自動化する場面で活躍します。表計算ソフトを開く手間を省けるのが利点です。

このセクションの用語

シェルスクリプト
ターミナルで実行するコマンドを順番に書いて自動化するファイルのことです。定型作業をまとめて実行できます。
使える場面 具体的な使い方
日次・月次の売上集計 groupbyでカテゴリ別の金額合計を出し、--outputで日付入りCSVに保存する
ログやアクセス数の傾向確認 数値列をsummaryにかけて合計・平均・最大値をまとめて把握する
大きなCSVからの抽出 filterで特定カテゴリの行だけ抜き出し、次の分析用に切り出す
定型レポートの下ごしらえ シェルスクリプトから4コマンドを順に呼び、集計済みCSVを毎回同じ手順で作る
さらに詳しく学べるPython研修の詳細はこちら

PythonのClickと標準csvによるCSV集計CLIツール開発のまとめ

PythonClickと標準ライブラリのcsvだけで、CSVを集計する小さなCLIcsvaggを作りました。@click.groupによるサブコマンド構成で、samplesummarygroupbyfilterの4つを1つのツールにまとめています。

数値列の自動判定とaggregate関数の使い回しにより、少ないコードで柔軟な集計を実現できました。文字コードをutf-8-sigにそろえたことで、日本語CSVもExcelで扱いやすくなっています。

実際にCLIツールを5回のコマンドで実行し、すべて正常に(終了コード0で)動作しました。まずは手元のCSVをsummaryにかけるところから、追体験してみてください。

さらに詳しく学べるPython研修の詳細はこちら

参考にした一次情報

  1. ^ Click公式ドキュメント. https://click.palletsprojects.com/en/stable/, (参照26-07-19).
  2. ^ Click: Commands and Groups(サブコマンドの作り方). https://click.palletsprojects.com/en/stable/commands/, (参照26-07-19).
  3. ^ Python公式ドキュメント: csv — CSVファイルの読み書き. https://docs.python.org/ja/3/library/csv.html, (参照26-07-19).

※内容は執筆時点のものです。ライブラリやサイトの仕様は変わる可能性があるため、公式ドキュメントもあわせてご確認ください。

ITやプログラミングに関するコラム


ITやプログラミングに関するニュース

ブログに戻る

コメントを残す

コメントは公開前に承認される必要があることにご注意ください。

企業・法人向けのIT・プログラミング・生成AI研修を探す、比較する - IT・プログラミングを知って学べるコネクトメディア CodeCampが提供するDX人材育成が可能なプログラミングやITが学べる公開講座 - IT・プログラミングを知って学べるコネクトメディア コードキャンプが提供する無料で学べるプログラミングスクール講座 - IT・プログラミングを知って学べるコネクトメディア コードキャンプDX人材育成研修 - IT・プログラミングを知って学べるコネクトメディア 3.5日の研修で、年間1,600時間の削減効果が見込まれる。東京きらぼしフィナンシャルグループのDX人材育成事例 - IT・プログラミングを知って学べるコネクトメディア 配属3ヶ月で30%の生産性向上を実現するいよぎんコンピュータサービスの新人研修に迫る - IT・プログラミングを知って学べるコネクトメディア 金融業界の業務効率化を加速するニッセイアセットマネジメントの生成AI×GAS活用研修事例 - IT・プログラミングを知って学べるコネクトメディア 【製造業のDX人材育成事例】デジタル人材の即戦力化を実現する、日本ガイシ株式会社の異動者向オンボーディング研修 - ITやプログラミングを知って学べるコネクトメディア フューチャーアーキテクト株式会社が実現した新入社員向けIT研修プログラムでタスクフォース制度が主体的な学びと成長を生み出す - IT・プログラミングを知って学べるコネクトメディア コードキャンプDX人材育成研修 - IT・プログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/【IT新入社員研修】オンラインとオフラインの最適バランスを実現したFutureOneの導入事例 - IT・プログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/【新入社員研修】柔軟なハイブリッド型Java研修で実現した新卒20名の成長と成果|サークレイス株式会社 - ITやプログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/現場により近いところにデジタルを根付かせるDX基礎講座研修|株式会社ブリヂストン - ITやプログラミングを知って学べるコネクトメディア コードキャンプIT・プログラミング研修事例/業務の効率化・DX推進に向けたIT人材育成への第一歩|株式会社カナエ - ITやプログラミングを知って学べるコネクトメディア 企業・法人向けのIT・プログラミング研修 - ITやプログラミングを知って学べるコネクトメディア

新着記事

対象者別で探す

子供(小学生・中学生・高校生)向け
プログラミング教室検索する

子供(小学生・中学生・高校生)がロボットやプログラミング言語を学ぶことができるオフラインからオンラインスクールを検索、比較することが可能です。

子供(小学生・中学生・高校生)
プログラミング教室検索する

ITやプログラムなどの
最新情報を検索する

日々、新しいITやプログラミング言語の情報が流れていきますが、特定の情報を時系列でニュースやコラムを確認することができます。

ITやプログラムなどの
最新情報を検索する