Skip to the content.

← 目次← 前: 10次: 12 →

11. ファイル入出力と標準ライブラリ

テーマ: 売上CSVの生成・読み込み・集計・書き出し

学習点: csv モジュール, with 文, collections(defaultdict/Counter), pathlib, datetime, sorted の key 引数

依存: 標準ライブラリのみ / 難易度: 中級

実行方法

uv run 11_csv_aggregate.py

スクリプト冒頭の PEP 723 メタデータ(# /// script)により、必要なライブラリは uv が自動的に仮想環境へ導入します。事前の pip install は不要です。

解説

何をするプログラムか

実務のデータ分析では、会計システムや POS から出力された CSV ファイルを読み込み、集計し、結果をまた CSV で共有する、という流れが最も基本的な作業です。このスクリプトは、その一連の流れ(生成 → 読み込み → 加工 → 集計 → 書き出し)を pandas などの外部ライブラリを一切使わず、Python の標準ライブラリだけで実装しています。

まず疑似的な売上データ 400 件を out_11/sales.csv に書き出し、それを読み直して地域別・商品別・月次の売上を集計し、最後に地域別サマリを summary_by_region.csv として出力します。標準ライブラリだけでどこまでできるかを知っておくと、pandas が使えない環境や小規模な処理で役立ちます。

コードの読みどころ

実行結果の見方

先頭行に生成された CSV のサイズ(17,773 bytes)、続いて総売上 95,903,700 円が表示されます。地域別売上では福岡が 28.8% で首位、シェアに比例した棒(█)で構成比が一目で分かります。商品別では単価 12.8 万円のノートPC が約 6,323 万円と、総売上の 3 分の 2 を占めることが読み取れます。

末尾の [出現回数] は金額ではなく取引件数ベースの上位 3 地域で、Counter の結果がタプルのリストで表示されます。最後に書き出した summary_by_region.csv の中身をそのまま表示しており、シェア(share 列)の合計が 1 になることを確認できます。

ソースコード

# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
"""11: ファイル入出力と標準ライブラリ -------------------------------------
テーマ: 売上CSVの生成・読み込み・集計・書き出し
学習点: csv モジュール, with 文, collections(defaultdict/Counter),
        pathlib, datetime, sorted の key 引数
"""
import csv
import random
from collections import Counter, defaultdict
from datetime import date, timedelta
from pathlib import Path

random.seed(7)
OUT = Path("out_11")
OUT.mkdir(exist_ok=True)

PRODUCTS = [("P001", "ノートPC", 128000), ("P002", "モニタ", 32000),
            ("P003", "キーボード", 8500), ("P004", "マウス", 4200),
            ("P005", "ドック", 21000)]
REGIONS = ["東京", "大阪", "名古屋", "福岡", "札幌"]


def generate(path: Path, n: int = 400) -> None:
    """疑似売上データを CSV に書き出す。"""
    start = date(2025, 1, 1)
    with path.open("w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["date", "region", "product_id", "product", "unit_price", "qty"])
        for _ in range(n):
            d = start + timedelta(days=random.randrange(365))
            pid, name, price = random.choice(PRODUCTS)
            w.writerow([d.isoformat(), random.choice(REGIONS), pid, name,
                        price, random.randint(1, 12)])


def main() -> None:
    src = OUT / "sales.csv"
    generate(src)
    print(f"生成: {src}  ({src.stat().st_size:,} bytes)")

    # DictReader は1行を辞書として返す
    with src.open(encoding="utf-8") as f:
        rows = list(csv.DictReader(f))
    for r in rows:
        r["amount"] = int(r["unit_price"]) * int(r["qty"])
        r["month"] = r["date"][:7]

    print(f"レコード数: {len(rows):,} / 総売上: {sum(r['amount'] for r in rows):,}円")

    by_region = defaultdict(int)
    by_product = defaultdict(int)
    by_month = defaultdict(int)
    for r in rows:
        by_region[r["region"]] += r["amount"]
        by_product[r["product"]] += r["amount"]
        by_month[r["month"]] += r["amount"]

    total = sum(by_region.values())
    print("\n[地域別売上]")
    for k, v in sorted(by_region.items(), key=lambda kv: -kv[1]):
        print(f"  {k:<5}{v:>13,}{v/total:>7.1%} {'█' * int(v/total*60)}")

    print("\n[商品別売上 上位3]")
    for k, v in sorted(by_product.items(), key=lambda kv: -kv[1])[:3]:
        print(f"  {k:<8}{v:>13,}円")

    print("\n[月次推移]")
    for m in sorted(by_month):
        v = by_month[m]
        print(f"  {m} {v:>12,}{'*' * int(v / 400000)}")

    print(f"\n[出現回数] {Counter(r['region'] for r in rows).most_common(3)}")

    # 集計結果を新しい CSV に書き出す
    dst = OUT / "summary_by_region.csv"
    with dst.open("w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=["region", "amount", "share"])
        w.writeheader()
        for k, v in sorted(by_region.items(), key=lambda kv: -kv[1]):
            w.writerow({"region": k, "amount": v, "share": f"{v/total:.4f}"})
    print(f"\n書き出し: {dst}")
    print(dst.read_text(encoding="utf-8"))


if __name__ == "__main__":
    main()

実行結果

生成: out_11/sales.csv  (17,773 bytes)
レコード数: 400 / 総売上: 95,903,700円

[地域別売上]
  福岡      27,637,900円   28.8% █████████████████
  東京      23,319,000円   24.3% ██████████████
  札幌      17,294,500円   18.0% ██████████
  大阪      14,580,600円   15.2% █████████
  名古屋     13,071,700円   13.6% ████████

[商品別売上 上位3]
  ノートPC      63,232,000円
  モニタ        16,832,000円
  ドック         8,883,000円

[月次推移]
  2025-01    9,868,700円 ************************
  2025-02    7,773,300円 *******************
  2025-03    4,526,400円 ***********
  2025-04   12,108,200円 ******************************
  2025-05    9,359,400円 ***********************
  2025-06    5,461,600円 *************
  2025-07    9,943,500円 ************************
  2025-08    6,817,500円 *****************
  2025-09    6,196,800円 ***************
  2025-10    5,680,000円 **************
  2025-11   11,466,800円 ****************************
  2025-12    6,701,500円 ****************

[出現回数] [('福岡', 94), ('大阪', 83), ('東京', 81)]

書き出し: out_11/summary_by_region.csv
region,amount,share
福岡,27637900,0.2882
東京,23319000,0.2432
札幌,17294500,0.1803
大阪,14580600,0.1520
名古屋,13071700,0.1363


← 目次← 前: 10次: 12 →