100本ノック / Python / データ分析のためのPython入門100本ノック

print関数で文字を表示する

ファイル操作と CSV で製造データを読み書きする Python 入門

100本ノック 第6章(No.051〜No.060):ファイル操作とCSV

本記事は「データ分析のための Python 入門 100本ノック」シリーズの 第6章 です。
第5章(No.041〜050)では関数・モジュール・例外処理を学びました。
本章では ファイルの読み書き・CSV の入出力 を、
自動車部品工場の品質検査データ・売上データを題材に学びます。

[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。

はじめに:この記事で扱う製造業の実務課題

自動車部品工場の品質管理担当 S さんは、毎日こんな作業をしています。

毎日の業務
  1. 各ラインの検査端末から出力された品質データ(CSV)を確認する
  2. 不良率が閾値を超えたラインの警告内容をテキストファイルに記録する
  3. 月末に品質検査 CSV を集計し、ライン別不良率サマリーを出力する
  4. 売上 CSV から月別の製品ライン別売上を集計して報告書を作成する

これらのファイル操作と CSV 処理を Python でマスターすることで、
手作業の集計・コピペ・転記から完全に解放 されます。

現場でよくある状況

場面課題
品質データ収集各ラインの検査端末から CSV が自動出力されるが、集計は手作業
アラート記録閾値超過時のアラート内容を Excel に手動コピペしている
月次集計月末に 30 日分の CSV を 1 行ずつ確認して合計している
結果書き出し集計結果を新しい CSV やテキストに手動でコピーしている
売上集計売上 CSV を月次でまとめる作業が毎月数時間かかっている

Python の open() / csv モジュールを使えば、これらを データ行数によらず自動処理 できます。

なぜこの問題は判断が難しいのか

「ファイルを開いて読むだけ」に見えますが、製造データの文脈では次の落とし穴があります。

  1. ファイルを閉じ忘れる問題
    f = open(...) の後にエラーが起きると f.close() が呼ばれず、
    ファイルハンドルが残り続けます。with 文を使えばこの問題を完全に防げます

  2. 文字コードの問題(UTF-8 vs Shift-JIS)
    工場の検査端末が出力する CSV は Shift-JIS(cp932)の場合があります
    open(..., encoding='utf-8')encoding='cp932' を明示するのが安全です

  3. CSV の値はすべて文字列型
    csv.reader で読み込んだ数値は '850' という文字列です
    int(row[4]) または float(row[6]) で数値型に変換する必要があります

  4. ヘッダー行を誤ってデータとして処理する
    最初の行(列名)を next(reader) でスキップしないと、
    int('生産数') のような変換エラーが発生します

  5. newline='' の省略による改行の二重化
    csv.writer を使う際に open(..., newline='') を省略すると
    Windows 環境で行末に空白行が挿入される問題が起きます

今回扱うノックの全体像

No.タイトル製造業での活用場面
051ファイルを読み込む生産日報テキストを open() で読み込む
052ファイルに書き込む品質アラートレポートをテキストファイルに書き出す
053with文でファイルを安全に扱うwith open() でライン集計サマリーを安全に読み書き
054CSVファイルの構造を理解する品質検査 CSV の生の構造(区切り・ヘッダー・列)を確認
055csvモジュールでCSVを読み込むcsv.reader で品質検査 CSV を読み込む
056CSVの行を1行ずつ処理する各行の不良率を判定してアラート件数を集計する
057CSVから特定の列を取り出す製品コード・生産数・不良率を列インデックスで抽出
058CSVの数値列を合計するライン別生産数・不良数を CSV から直接集計・可視化
059CSVに処理結果を書き出す集計サマリーを新しい CSV ファイルに書き出す
060売上CSVから月別売上を集計するDictReader で売上 CSV を月別・製品別に集計しグラフ化

Python 環境の準備

import subprocess, sys

res = subprocess.run(["sw_vers", "-productVersion"], capture_output=True, text=True)
print(f"macOS : {res.stdout.strip()}")
print(f"Python: {sys.version}")
macOS : 26.3
Python: 3.13.1 (main, Dec  3 2024, 17:59:52) [Clang 16.0.0 (clang-1600.0.26.4)]
import csv
import os
import tempfile
from collections import defaultdict
from datetime import date, timedelta

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

matplotlib.rcParams['font.family'] = 'Hiragino Maru Gothic Pro'
%config InlineBackend.figure_format = 'svg'
np.random.seed(42)

print("ライブラリ読み込み完了")
print(f"  NumPy      : {np.__version__}")
print(f"  Matplotlib : {matplotlib.__version__}")
ライブラリ読み込み完了
  NumPy      : 2.5.1
  Matplotlib : 3.11.0

架空データの作成

想定シナリオ: 自動車部品工場 / 品質管理・売上管理チーム
データ種別: 生産日報テキスト・品質検査 CSV(3 ヶ月分)・売上 CSV(3 ヶ月分)

ファイルはシステムの一時ディレクトリ(tempfile.gettempdir())に保存します。
実務では共有フォルダや所定のディレクトリのパスを指定します。

# ===== ファイル保存先 =====
WORK_DIR = tempfile.gettempdir()
REPORT_TXT = os.path.join(WORK_DIR, "kobo_daily_report.txt")
QUALITY_CSV = os.path.join(WORK_DIR, "kobo_quality_check.csv")
SUMMARY_CSV = os.path.join(WORK_DIR, "kobo_quality_summary.csv")
SALES_CSV = os.path.join(WORK_DIR, "kobo_monthly_sales.csv")

np.random.seed(42)

# ===== ライン・製品マスタ =====
lines_config = [
    {"line": "Aライン", "code": "EG-1001", "name": "エンジン部品A", "plan": 850, "price": 4200, "defect_base": 0.012},
    {"line": "Bライン", "code": "EG-1002", "name": "エンジン部品B", "plan": 720, "price": 3800, "defect_base": 0.018},
    {
        "line": "Cライン",
        "code": "HN-2001",
        "name": "ハーネスユニット",
        "plan": 1200,
        "price": 1500,
        "defect_base": 0.025,
    },
    {
        "line": "Dライン",
        "code": "SN-3001",
        "name": "センサーモジュール",
        "plan": 950,
        "price": 2800,
        "defect_base": 0.015,
    },
    {"line": "Eライン", "code": "FR-4001", "name": "フレーム部品", "plan": 630, "price": 5500, "defect_base": 0.010},
]


# ===== 2025年1〜3月の営業日生成 =====
def business_days(start, end):
    days = []
    d = start
    while d <= end:
        if d.weekday() < 5:
            days.append(d)
        d += timedelta(days=1)
    return days


bdays = business_days(date(2025, 1, 6), date(2025, 3, 31))

# ===== 品質検査 CSV の生成(330行前後)=====
quality_rows = []
for d in bdays:
    for lc in lines_config:
        actual = int(lc["plan"] * np.random.uniform(0.92, 1.08))
        dr = lc["defect_base"] * np.random.uniform(0.5, 2.5)
        defect = int(actual * dr)
        quality_rows.append(
            {
                "日付": d.isoformat(),
                "ライン": lc["line"],
                "製品コード": lc["code"],
                "製品名": lc["name"],
                "生産数": actual,
                "不良数": defect,
                "不良率": round(dr * 100, 2),
            }
        )

with open(QUALITY_CSV, "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["日付", "ライン", "製品コード", "製品名", "生産数", "不良数", "不良率"])
    writer.writeheader()
    writer.writerows(quality_rows)

# ===== 生産日報テキストの生成 =====
last_date = bdays[-1]
last_rows = [r for r in quality_rows if r["日付"] == last_date.isoformat()]
report_lines_list = [
    f"=== {last_date.isoformat()} 生産日報 ===",
    "担当者: 田中 太郎",
    "承認者: 山本 部長",
    "",
    "【ライン別生産実績】",
]
for r in last_rows:
    report_lines_list.append(
        f"  {r['ライン']}: 生産 {r['生産数']:,}個 / 不良 {r['不良数']}個 / 不良率 {r['不良率']:.2f}%"
    )
report_lines_list += [
    "",
    "【総括】",
    "本日の全ライン生産は概ね計画通りに推移しました。",
    "Cラインの不良率が前日比で上昇傾向のため、明日朝に点検を実施します。",
]
with open(REPORT_TXT, "w", encoding="utf-8") as f:
    f.write("\n".join(report_lines_list))

# ===== 売上 CSV の生成 =====
sales_rows = []
for d in bdays:
    for lc in lines_config:
        qty = int(lc["plan"] * np.random.uniform(0.85, 1.15))
        amount = qty * lc["price"]
        sales_rows.append(
            {
                "日付": d.isoformat(),
                "製品コード": lc["code"],
                "製品名": lc["name"],
                "数量": qty,
                "単価": lc["price"],
                "金額": amount,
            }
        )

with open(SALES_CSV, "w", newline="", encoding="utf-8") as f:
    writer = csv.DictWriter(f, fieldnames=["日付", "製品コード", "製品名", "数量", "単価", "金額"])
    writer.writeheader()
    writer.writerows(sales_rows)

print("=== 架空データ生成完了 ===")
print(f"品質検査 CSV : {QUALITY_CSV}")
print(f"  行数 : {len(quality_rows):,} 行(ヘッダー除く)")
print(f"生産日報 TXT : {REPORT_TXT}")
print(f"売上 CSV     : {SALES_CSV}")
print(f"  行数 : {len(sales_rows):,} 行(ヘッダー除く)")
print(f"営業日数     : {len(bdays)} 日(2025-01-06〜2025-03-31)")
=== 架空データ生成完了 ===
品質検査 CSV : /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_quality_check.csv
  行数 : 305 行(ヘッダー除く)
生産日報 TXT : /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_daily_report.txt
売上 CSV     : /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_monthly_sales.csv
  行数 : 305 行(ヘッダー除く)
営業日数     : 61 日(2025-01-06〜2025-03-31)

No.051:ファイルを読み込む

実務での意味

工場の検査端末や生産管理システムは、業務の区切りごとにログや日報を
テキストファイル として出力することがあります。
open() で読み込む操作は、Python でのファイル処理の出発点です。

分析・モデル化の考え方

open(ファイルパス, モード, encoding=エンコード) でファイルを開き、

  • read() — ファイル全体を1つの文字列として読み込む
  • readlines() — 行ごとのリストとして読み込む
  • readline() — 1行ずつ読み込む

の3通りの読み込み方があります。テキストファイルの確認には read() が便利です。

Python で確認する

# No.051: open() でファイルを読み込む
print(f"=== No.051: ファイルを読み込む ===")
print(f"読み込みファイル: {REPORT_TXT}")
print()

# open() で読み込みモード ("r") でファイルを開く
f = open(REPORT_TXT, "r", encoding="utf-8")
content = f.read()  # ファイル全体を1つの文字列として読み込む
f.close()  # 必ず close() する(with 文の前段階として理解する)

print(content)
print()
print(f"--- ファイル情報 ---")
print(f"文字数: {len(content):,} 文字")
print(f"行数  : {len(content.splitlines())} 行")
=== No.051: ファイルを読み込む ===
読み込みファイル: /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_daily_report.txt

=== 2025-03-31 生産日報 ===
担当者: 田中 太郎
承認者: 山本 部長

【ライン別生産実績】
  Aライン: 生産 804個 / 不良 10個 / 不良率 1.27%
  Bライン: 生産 682個 / 不良 8個 / 不良率 1.22%
  Cライン: 生産 1,127個 / 不良 40個 / 不良率 3.55%
  Dライン: 生産 905個 / 不良 16個 / 不良率 1.84%
  Eライン: 生産 630個 / 不良 11個 / 不良率 1.88%

【総括】
本日の全ライン生産は概ね計画通りに推移しました。
Cラインの不良率が前日比で上昇傾向のため、明日朝に点検を実施します。

--- ファイル情報 ---
文字数: 309 文字
行数  : 14 行

結果の読み取り

生産日報テキスト(kobo_daily_report.txt)が 309文字 / 14行 として読み込まれました。
f.read() は改行を含む1つの文字列として返すため、print() するとファイルの内容がそのまま表示されます。
最終営業日(2025-03-31)の実績では Cライン(3.55%) が警告ラインを超えており、
総括欄に自動で「点検実施」の記載が含まれています。
ただし、f.close() の呼び忘れに注意が必要です — この問題を解決するのが with 文(No.053)です。


No.052:ファイルに書き込む

実務での意味

品質管理では 警告内容をテキストファイルに記録・保存 することが求められます。
毎日の品質アラートを自動でファイルに書き出すことで、
担当者が後からエビデンスとして参照できるようになります。

分析・モデル化の考え方

open(パス, 'w') で書き込みモードでファイルを開き、write() で文字列を書き込みます。

  • 'w' モード: 上書き(ファイルが存在すれば内容を消去して新規作成)
  • 'a' モード: 追記(ファイルが存在すれば末尾に追加)
    日次ログの追記には 'a'、月次レポートの再作成には 'w' を使い分けます。

Python で確認する

# No.052: open() でファイルに書き込む
ALERT_TXT = os.path.join(WORK_DIR, "kobo_quality_alert.txt")

ALERT_THRESHOLD = 3.0  # 不良率アラート閾値(%)

# 最新日(最終営業日)の品質データを取得
latest_date = max(r["日付"] for r in quality_rows)
latest_rows = [r for r in quality_rows if r["日付"] == latest_date]

# 書き込みモード ("w") でファイルを開く
f = open(ALERT_TXT, "w", encoding="utf-8")
f.write(f"=== 品質アラートレポート ({latest_date}) ===\n")
f.write(f"判定閾値: 不良率 {ALERT_THRESHOLD}% 以上\n\n")

alert_count = 0
for r in latest_rows:
    dr = float(r["不良率"])
    if dr >= ALERT_THRESHOLD:
        f.write(f"[警告] {r['ライン']}{r['製品コード']}): 不良率 {dr:.2f}%\n")
        alert_count += 1

if alert_count == 0:
    f.write("本日は全ラインが正常範囲内です\n")

f.write(f"\n警告ライン数: {alert_count}\n")
f.close()  # close() を忘れないこと

print(f"書き込み完了: {ALERT_TXT}")
print()

# 書き込み結果を確認のために読み込む
f_read = open(ALERT_TXT, "r", encoding="utf-8")
print(f_read.read())
f_read.close()
書き込み完了: /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_quality_alert.txt

=== 品質アラートレポート (2025-03-31) ===
判定閾値: 不良率 3.0% 以上

[警告] Cライン(HN-2001): 不良率 3.55%

警告ライン数: 1 本

結果の読み取り

最終営業日の各ラインを走査し、不良率 3.0% 以上のラインを
[警告] フラグとしてテキストファイルに書き出しました。
'w' モードのため、同じコードを再実行するたびにファイルは上書きされます。
アラートの累積ログを残したい場合は 'a'(追記)モードに変更すると、
実行ごとに末尾に追記されるようになります。


No.053:with文でファイルを安全に扱う

実務での意味

open() でファイルを開いた後、処理中に例外が発生した場合
f.close() が呼ばれずにファイルハンドルが残り続けます。
with 文を使えば、ブロックを抜けるときに(例外発生時も)自動でクローズされます。
製造データ処理では with open() as f: を常に使うのがベストプラクティスです。

分析・モデル化の考え方

with open(...) as f:コンテキストマネージャー を使った構文です。
with ブロックを抜けると自動的に f.__exit__() が呼ばれ、ファイルがクローズされます。

  • ファイル処理は常に with 文を使う
  • for line in f: で1行ずつ読み込む(メモリ効率が良い)

Python で確認する

# No.053: with 文でファイルを安全に読み書きする
SUMMARY_TXT = os.path.join(WORK_DIR, "kobo_line_summary.txt")

print("=== No.053: with 文でファイルを安全に扱う ===")
print()

# --- 書き込み ---
# with ブロックを抜けると自動的に f.close() が呼ばれる
with open(SUMMARY_TXT, "w", encoding="utf-8") as f:
    f.write("=== ライン別 品質集計サマリー(2025年1〜3月)===\n\n")
    for lc in lines_config:
        rows = [r for r in quality_rows if r["ライン"] == lc["line"]]
        total_p = sum(r["生産数"] for r in rows)
        total_d = sum(r["不良数"] for r in rows)
        avg_dr = total_d / total_p * 100 if total_p else 0.0
        f.write(f"  {lc['line']} ({lc['code']}): 累計生産 {total_p:,}個  平均不良率 {avg_dr:.2f}%\n")
    f.write("\n--- 生成完了 ---\n")
# ← ここで自動的に f.close() が実行される

print(f"書き込み完了: {SUMMARY_TXT}")
print()

# --- 読み込み(1行ずつ)---
print("=== ファイル内容(1行ずつ読み込み)===")
with open(SUMMARY_TXT, "r", encoding="utf-8") as f:
    for line_text in f:  # ファイルを1行ずつ読み込む
        print(line_text, end="")  # 改行を重複させないために end="" を指定
=== No.053: with 文でファイルを安全に扱う ===

書き込み完了: /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_line_summary.txt

=== ファイル内容(1行ずつ読み込み)===
=== ライン別 品質集計サマリー(2025年1〜3月)===

  Aライン (EG-1001): 累計生産 51,690個  平均不良率 1.89%
  Bライン (EG-1002): 累計生産 43,727個  平均不良率 2.71%
  Cライン (HN-2001): 累計生産 73,157個  平均不良率 3.85%
  Dライン (SN-3001): 累計生産 57,698個  平均不良率 2.17%
  Eライン (FR-4001): 累計生産 38,546個  平均不良率 1.29%

--- 生成完了 ---

結果の読み取り

with open(...) as f: の書き込みブロックで 5 ラインの 3 ヶ月サマリーが生成されました。
Cライン(HN-2001)の平均不良率 3.85% が最大で、閾値 3.0% を超えています。
Eライン(FR-4001)は 1.29% で最も安定しており、Aライン(1.89%)と合わせて優良ラインです。
for line_text in f:ファイルを1行ずつ読み込むイテレータ として動作します。
100万行の大容量 CSV でも、f.read() でメモリに全展開するより
for line in f: の逐次読み込みの方がメモリ効率が高く実用的です。


No.054:CSVファイルの構造を理解する

実務での意味

工場の検査システムや ERP から出力される CSV を処理する前に、
列の構成・データ型・区切り文字 を確認することが不可欠です。
生の CSV を open() で読んで構造を確認するのが最初のステップです。

分析・モデル化の考え方

CSV(Comma-Separated Values)の基本構造:

  • 1行目: ヘッダー行(列名)
  • 2行目以降: データ行(カンマ区切りの値)
  • 各値はカンマ , で区切られる(値にカンマを含む場合は "..." で囲む)

open() で生の文字列として読み込み split(",") で分割すると、
CSV の構造が直感的に確認できます。

Python で確認する

# No.054: CSV ファイルの構造を生の文字列で確認する
print(f"=== No.054: CSV ファイルの構造を理解する ===")
print(f"ファイル: {QUALITY_CSV}")
print()

with open(QUALITY_CSV, "r", encoding="utf-8") as f:
    all_lines = f.readlines()

total_rows = len(all_lines) - 1  # ヘッダー除く
print(f"総行数: {len(all_lines)} 行(ヘッダー 1 行 + データ {total_rows} 行)")
print()

# 生の文字列として先頭 5 行を表示
print("=== 先頭 5 行の生データ(カンマ区切りの構造)===")
for i, line in enumerate(all_lines[:5]):
    print(f"  [{i}] {line.rstrip()}")

print()

# ヘッダーを split して列インデックスを確認
header_raw = all_lines[0].rstrip().split(",")
print("=== 列インデックスの確認 ===")
for i, col in enumerate(header_raw):
    print(f"  列[{i}]: {col}")

print()

# データの 1 行を分解して確認
sample = all_lines[1].rstrip().split(",")
print("=== 1 行のデータを列に分解した結果 ===")
for col, val in zip(header_raw, sample):
    typ = "数値" if val.replace(".", "").replace("-", "").isdigit() else "文字列"
    print(f"  {col}: {val!r}  ({typ})")
=== No.054: CSV ファイルの構造を理解する ===
ファイル: /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_quality_check.csv

総行数: 306 行(ヘッダー 1 行 + データ 305 行)

=== 先頭 5 行の生データ(カンマ区切りの構造)===
  [0] 日付,ライン,製品コード,製品名,生産数,不良数,不良率
  [1] 2025-01-06,Aライン,EG-1001,エンジン部品A,832,23,2.88
  [2] 2025-01-06,Bライン,EG-1002,エンジン部品B,746,22,3.06
  [3] 2025-01-06,Cライン,HN-2001,ハーネスユニット,1133,22,2.03
  [4] 2025-01-06,Dライン,SN-3001,センサーモジュール,882,29,3.35

=== 列インデックスの確認 ===
  列[0]: 日付
  列[1]: ライン
  列[2]: 製品コード
  列[3]: 製品名
  列[4]: 生産数
  列[5]: 不良数
  列[6]: 不良率

=== 1 行のデータを列に分解した結果 ===
  日付: '2025-01-06'  (数値)
  ライン: 'Aライン'  (文字列)
  製品コード: 'EG-1001'  (文字列)
  製品名: 'エンジン部品A'  (文字列)
  生産数: '832'  (数値)
  不良数: '23'  (数値)
  不良率: '2.88'  (数値)

結果の読み取り

CSV ファイルの全行数(ヘッダー1行 + データ行)が確認できました。
[0] が「日付」、列 [6] が「不良率」というインデックス構造が把握できます。
split(",") の結果はすべて 文字列型 であることに注意してください。
生産数(列[4])も不良率(列[6])も '850' '1.22' といった文字列です。
csv.reader(No.055)や int() / float() での型変換(No.055〜)が必要です。


No.055:csvモジュールでCSVを読み込む

実務での意味

csv モジュールを使うと、カンマを含む値の "..." クォート処理などを
自動的にハンドリングしてくれます。
製造データの製品名やメモ欄にカンマが含まれる場合でも正しく解析できます。

分析・モデル化の考え方

csv.reader(f) はファイルオブジェクトからイテレータを生成し、
行を ['値1', '値2', ...] というリストとして返します。

  • next(reader) でヘッダー行を1行読み飛ばす
  • for row in reader: で各データ行を処理する

newline=''open() に指定するのは csv.reader を使う際の慣習です。

Python で確認する

# No.055: csv.reader で CSV を読み込む
print("=== No.055: csv.reader で CSV を読み込む ===")
print()

rows_055 = []
with open(QUALITY_CSV, "r", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    header_055 = next(reader)  # ヘッダー行を取得(スキップ)
    for row in reader:
        rows_055.append(row)  # 各行をリストとして格納

print(f"ヘッダー  : {header_055}")
print(f"データ件数: {len(rows_055):,} 行")
print()
print("=== 先頭 5 行(csv.reader で読み込んだリスト)===")
fmt = f"  {{:<12}} {{:<10}} {{:<10}} {{:>8}} {{:>8}} {{:>8}}"
print(fmt.format(header_055[0], header_055[1], header_055[2], header_055[4], header_055[5], header_055[6]))
print("  " + "-" * 62)
for row in rows_055[:5]:
    print(fmt.format(row[0], row[1], row[2], row[4], row[5], row[6]))
print()

# ヘッダー行の型を確認
print("=== csv.reader の行の型 ===")
print(f"  ヘッダー行の型  : {type(header_055)}, 要素の型: {type(header_055[0])}")
print(f"  データ行の型    : {type(rows_055[0])}, 要素の型: {type(rows_055[0][4])!r} <- 数値でも文字列!")
=== No.055: csv.reader で CSV を読み込む ===

ヘッダー  : ['日付', 'ライン', '製品コード', '製品名', '生産数', '不良数', '不良率']
データ件数: 305 行

=== 先頭 5 行(csv.reader で読み込んだリスト)===
  日付           ライン        製品コード           生産数      不良数      不良率
  --------------------------------------------------------------
  2025-01-06   Aライン       EG-1001         832       23     2.88
  2025-01-06   Bライン       EG-1002         746       22     3.06
  2025-01-06   Cライン       HN-2001        1133       22     2.03
  2025-01-06   Dライン       SN-3001         882       29     3.35
  2025-01-06   Eライン       FR-4001         640       12     1.92

=== csv.reader の行の型 ===
  ヘッダー行の型  : <class 'list'>, 要素の型: <class 'str'>
  データ行の型    : <class 'list'>, 要素の型: <class 'str'> <- 数値でも文字列!

結果の読み取り

csv.reader でヘッダー行とデータ行がそれぞれリストとして読み込めました。
重要な点として、生産数「850」も不良率「1.22」も文字列型 で返されることが確認できます。
int(row[4])float(row[6]) による明示的な型変換が必要です。
csv.reader は区切り文字・クォート処理を自動で行うため、
split(",") より安全で確実な CSV 読み込み手段です。


No.056:CSVの行を1行ずつ処理する

実務での意味

品質検査 CSV の 全 330 行を1行ずつ走査して不良率アラートを集計 するのは、
毎朝の品質確認自動化の核心です。for row in reader: という行単位の処理ループで、
データ件数が増えてもコードを変える必要はありません。

分析・モデル化の考え方

CSV の各行に対して判定・集計処理を適用するのが「行単位処理」です。

  • アラート件数のカウント: 条件付き if + カウンタ変数
  • データの絞り込み: 条件を満たす行だけ別リストに蓄積
  • 集計: 行ごとに数値を足し込む(No.058 で詳述)

Python で確認する

# No.056: CSV の行を 1 行ずつ処理して不良率アラートを集計する
print("=== No.056: CSV の行を 1 行ずつ処理する ===")
print()

DEFECT_THRESHOLD_056 = 3.0  # 不良率アラート閾値(%)

alert_rows_056 = []
normal_count_056 = 0
alert_count_056 = 0

with open(QUALITY_CSV, "r", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    next(reader)  # ヘッダーをスキップ
    for row in reader:
        rate_f = float(row[6])  # 列[6] = 不良率(文字列→float に変換)
        if rate_f >= DEFECT_THRESHOLD_056:
            alert_rows_056.append(
                {
                    "日付": row[0],
                    "ライン": row[1],
                    "製品コード": row[2],
                    "不良率": rate_f,
                }
            )
            alert_count_056 += 1
        else:
            normal_count_056 += 1

total_056 = alert_count_056 + normal_count_056
print(f"全検査レコード : {total_056:,} 件")
print(f"正常 (< {DEFECT_THRESHOLD_056}%)  : {normal_count_056:,} 件")
print(f"アラート (>= {DEFECT_THRESHOLD_056}%): {alert_count_056:,} 件  ({alert_count_056/total_056*100:.1f}%)")
print()

# ラインごとのアラート件数
line_alerts = defaultdict(int)
for r in alert_rows_056:
    line_alerts[r["ライン"]] += 1

print(f"=== ライン別アラート件数(不良率 >= {DEFECT_THRESHOLD_056}% の日数)===")
print(f"{'ライン':<10} {'アラート件数':>12} {'全体比':>8}")
print("-" * 34)
for line_name in sorted(line_alerts.keys()):
    cnt = line_alerts[line_name]
    bdays_cnt = total_056 // len(lines_config)
    pct = cnt / bdays_cnt * 100
    print(f"{line_name:<10} {cnt:>12}{pct:>7.1f}%")

print()
print(f"=== 不良率 TOP5 ===")
top5 = sorted(alert_rows_056, key=lambda x: x["不良率"], reverse=True)[:5]
for i, r in enumerate(top5, 1):
    print(f"  {i}位: {r['日付']} {r['ライン']} ({r['製品コード']}): {r['不良率']:.2f}%")
=== No.056: CSV の行を 1 行ずつ処理する ===

全検査レコード : 305 件
正常 (< 3.0%)  : 220 件
アラート (>= 3.0%): 85 件  (27.9%)

=== ライン別アラート件数(不良率 >= 3.0% の日数)===
ライン              アラート件数      全体比
----------------------------------
Aライン                  1 件     1.6%
Bライン                 25 件    41.0%
Cライン                 44 件    72.1%
Dライン                 15 件    24.6%

=== 不良率 TOP5 ===
  1位: 2025-03-12 Cライン (HN-2001): 6.21%
  2位: 2025-02-17 Cライン (HN-2001): 6.13%
  3位: 2025-03-26 Cライン (HN-2001): 6.00%
  4位: 2025-03-07 Cライン (HN-2001): 5.99%
  5位: 2025-03-20 Cライン (HN-2001): 5.97%

結果の読み取り

305 件の検査レコードを全行走査した結果、正常 220 件・アラート 85 件(27.9%) と集計されました。
Cライン(HN-2001: ハーネスユニット)は 44件(72.1%) でアラート頻度が最大です。
最大不良率は 2025-03-12 の Cライン 6.21% で、通常の約 2.5 倍に達しています。
Aラインのアラートは 1 件のみで、Eライン(FR-4001)はアラートゼロの安定稼働です。
float(row[6]) による型変換と if rate_f >= DEFECT_THRESHOLD_056: による振り分けが、
行単位 CSV 処理の典型パターンです。


No.057:CSVから特定の列を取り出す

実務での意味

全列ではなく 「製品コード・生産数・不良率」だけを抽出 して
製品別の平均生産数・平均不良率を集計するのは、品質管理レポート作成の典型作業です。
列インデックスを定数として定義することで、列が増減した場合の修正が容易になります。

分析・モデル化の考え方

row[インデックス] で特定列を取り出します。
列インデックスを定数変数として定義(例: IDX_PROD = 4)すると、
CSV の列順が変わっても1か所の変更で対応できます。
集計には defaultdict を活用することで辞書の初期化コードを省略できます。

Python で確認する

# No.057: CSV から特定の列を取り出して製品コード別集計を行う

# 列インデックスを定数として定義(CSV 列構成が変わっても1か所だけ修正すればよい)
IDX_DATE = 0
IDX_LINE = 1
IDX_CODE = 2
IDX_NAME = 3
IDX_PROD = 4
IDX_DEFECT = 5
IDX_RATE = 6

print("=== No.057: CSV から特定の列を取り出す ===")
print()

# 製品コード・生産数・不良率の3列だけを取り出す
code_stats = defaultdict(lambda: {"生産数": 0, "不良率合計": 0.0, "件数": 0, "名前": ""})

with open(QUALITY_CSV, "r", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    next(reader)
    for row in reader:
        code = row[IDX_CODE]
        prod = int(row[IDX_PROD])  # 列[4]を int に変換
        rate = float(row[IDX_RATE])  # 列[6]を float に変換
        code_stats[code]["生産数"] += prod
        code_stats[code]["不良率合計"] += rate
        code_stats[code]["件数"] += 1
        code_stats[code]["名前"] = row[IDX_NAME]

print("=== 製品コード別 累計生産数・平均不良率 ===")
print(f"{'コード':<10} {'製品名':<18} {'累計生産数':>12} {'平均不良率':>10}")
print("-" * 56)
for code in sorted(code_stats.keys()):
    s = code_stats[code]
    avg = s["不良率合計"] / s["件数"]
    print(f"{code:<10} {s['名前']:<18} {s['生産数']:>12,}{avg:>9.2f}%")
=== No.057: CSV から特定の列を取り出す ===

=== 製品コード別 累計生産数・平均不良率 ===
コード        製品名                       累計生産数      平均不良率
--------------------------------------------------------
EG-1001    エンジン部品A                  51,690 個      1.96%
EG-1002    エンジン部品B                  43,727 個      2.78%
FR-4001    フレーム部品                   38,546 個      1.37%
HN-2001    ハーネスユニット                 73,157 個      3.89%
SN-3001    センサーモジュール                57,698 個      2.22%

結果の読み取り

5 つの製品コードそれぞれについて、3 ヶ月分の累計生産数と平均不良率が集計されました。
IDX_PROD = 4 のように列インデックスを定数化することで、
CSV のフォーマットが変わった際の保守性が向上します。
defaultdict(lambda: {...}) は辞書の初期化処理を省略できる実務的なテクニックです。
平均不良率は HN-2001(ハーネスユニット)が最も高くなることが予想され、
重点管理ラインの特定に役立ちます。


No.058:CSVの数値列を合計する

実務での意味

ライン別の 累計生産数と累計不良数を CSV から直接集計 するのは、
月次品質報告書の自動生成に直結する処理です。
集計結果をグラフで可視化することで、「どのラインが最もリスクか」が直感的に把握できます。

分析・モデル化の考え方

CSV の数値列を合計する基本パターンは
total += int(row[i]) のシンプルな累積加算です。

  • int() / float() による型変換を忘れない
  • ライン別集計には辞書を使う(line_totals[line]["生産数"] += prod

Python で確認する

# No.058: CSV の数値列(生産数・不良数)をライン別に合計する
print("=== No.058: CSV の数値列を合計する ===")
print()

total_prod = 0
total_defect = 0
line_totals = {}

with open(QUALITY_CSV, "r", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    next(reader)
    for row in reader:
        prod = int(row[IDX_PROD])
        defect = int(row[IDX_DEFECT])
        line = row[IDX_LINE]
        total_prod += prod
        total_defect += defect
        if line not in line_totals:
            line_totals[line] = {"生産数": 0, "不良数": 0, "コード": row[IDX_CODE], "名前": row[IDX_NAME]}
        line_totals[line]["生産数"] += prod
        line_totals[line]["不良数"] += defect

overall_rate = total_defect / total_prod * 100

print("=== ライン別 生産累計(2025年1〜3月)===")
print(f"{'ライン':<10} {'生産数合計':>12} {'不良数合計':>10} {'不良率':>8}")
print("-" * 46)
for line in sorted(line_totals.keys()):
    t = line_totals[line]
    rate = t["不良数"] / t["生産数"] * 100
    print(f"{line:<10} {t['生産数']:>12,}{t['不良数']:>8}{rate:>7.2f}%")
print("-" * 46)
print(f"{'全ライン合計':<10} {total_prod:>12,}{total_defect:>8}{overall_rate:>7.2f}%")
=== No.058: CSV の数値列を合計する ===

=== ライン別 生産累計(2025年1〜3月)===
ライン               生産数合計      不良数合計      不良率
----------------------------------------------
Aライン             51,690 個      978 個    1.89%
Bライン             43,727 個     1185 個    2.71%
Cライン             73,157 個     2817 個    3.85%
Dライン             57,698 個     1253 個    2.17%
Eライン             38,546 個      497 個    1.29%
----------------------------------------------
全ライン合計          264,818 個     6730 個    2.54%
# No.058: ライン別生産数・不良率の可視化
sorted_lines = sorted(line_totals.keys())
prods = [line_totals[l]["生産数"] for l in sorted_lines]
defects = [line_totals[l]["不良数"] for l in sorted_lines]
rates = [line_totals[l]["不良数"] / line_totals[l]["生産数"] * 100 for l in sorted_lines]

fig, axes = plt.subplots(1, 2, figsize=(13, 5))
x_pos = list(range(len(sorted_lines)))

# 左: 生産数(積み上げ: 良品数 + 不良数)
good_counts = [p - d for p, d in zip(prods, defects)]
axes[0].bar(x_pos, [g / 1000 for g in good_counts], color="#2E86AB", alpha=0.85, label="良品数")
axes[0].bar(
    x_pos,
    [d / 1000 for d in defects],
    color="#E74C3C",
    alpha=0.85,
    label="不良数",
    bottom=[g / 1000 for g in good_counts],
)
for i, p in enumerate(prods):
    axes[0].text(i, p / 1000 + 0.5, f"{p/1000:.1f}k", ha="center", va="bottom", fontsize=9)
axes[0].set_title("ライン別 3ヶ月累計生産数(良品/不良 内訳)", fontsize=13, pad=10)
axes[0].set_xlabel("ライン", fontsize=11)
axes[0].set_ylabel("生産数(千個)", fontsize=11)
axes[0].set_xticks(x_pos)
axes[0].set_xticklabels(sorted_lines, fontsize=10)
axes[0].legend(fontsize=10)
axes[0].grid(axis="y", alpha=0.3)

# 右: 不良率(棒グラフ + 閾値ライン)
bar_colors = ["#E74C3C" if r >= 3.0 else "#2E86AB" for r in rates]
axes[1].bar(x_pos, rates, color=bar_colors, alpha=0.85)
axes[1].axhline(y=3.0, color="red", linewidth=2.0, linestyle="--", label="警告ライン (3.0%)")
axes[1].axhline(y=overall_rate, color="gray", linewidth=1.5, linestyle=":", label=f"全体平均 ({overall_rate:.2f}%)")
for i, r in enumerate(rates):
    axes[1].text(i, r + 0.05, f"{r:.2f}%", ha="center", va="bottom", fontsize=9)
axes[1].set_title("ライン別 3ヶ月平均不良率", fontsize=13, pad=10)
axes[1].set_xlabel("ライン", fontsize=11)
axes[1].set_ylabel("不良率(%)", fontsize=11)
axes[1].set_xticks(x_pos)
axes[1].set_xticklabels(sorted_lines, fontsize=10)
axes[1].legend(fontsize=9)
axes[1].grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()

svg

結果の読み取り

3 ヶ月分(61 営業日 × 5 ライン = 305 レコード)の CSV を全行集計した結果、
全ライン合計 264,818 個・全体不良率 2.54% となりました。
Cライン(HN-2001)の不良率 3.85% が唯一 3.0% 閾値を超え、赤バーで可視化されます。
Eライン(FR-4001)は不良率 1.29% で最も安定しています。
左グラフの積み上げ棒では、計画 1,200 個/日の Cライン が全体生産数(73,157 個)で最大規模であることが確認できます。
int(row[IDX_PROD]) という型変換パターンと辞書を使ったライン別集計が CSV 数値処理の基本形です。


No.059:CSVに処理結果を書き出す

実務での意味

集計結果を 新しい CSV ファイルに書き出す ことで、
他のツール(Excel・BIシステム・ERP)との連携が可能になります。
手作業でコピペしていた「集計表の転記」をコードで完全に自動化できます。

分析・モデル化の考え方

csv.writer(f) でライターオブジェクトを生成し、

  • writer.writerow([値1, 値2, ...]) — 1行を書き込む
  • writer.writerows([[...], [...], ...]) — 複数行を一括で書き込む

ヘッダー行を最初に writerow し、データ行を続けるのが基本パターンです。
open(..., newline='') を指定することで、Windows 環境での空白行挿入を防ぎます。

Python で確認する

# No.059: 集計結果を CSV ファイルに書き出す
print("=== No.059: CSV に処理結果を書き出す ===")
print()

with open(SUMMARY_CSV, "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)

    # ヘッダー行
    writer.writerow(["ライン", "製品コード", "製品名", "生産数合計", "不良数合計", "不良率", "良品数合計"])

    # データ行(ライン別)
    for line in sorted(line_totals.keys()):
        t = line_totals[line]
        rate = round(t["不良数"] / t["生産数"] * 100, 2)
        good = t["生産数"] - t["不良数"]
        writer.writerow([line, t["コード"], t["名前"], t["生産数"], t["不良数"], rate, good])

    # 合計行
    writer.writerow(
        ["全ライン合計", "", "", total_prod, total_defect, round(overall_rate, 2), total_prod - total_defect]
    )

print(f"書き込み完了: {SUMMARY_CSV}")
print()

# 書き出した CSV を読み込んで確認
print("=== 書き出したCSVの内容(csv.reader で確認)===")
with open(SUMMARY_CSV, "r", encoding="utf-8", newline="") as f:
    reader = csv.reader(f)
    for i, row in enumerate(reader):
        prefix = "ヘッダー" if i == 0 else f"行 [{i:>2}]"
        print(f"  {prefix}: {row}")
=== No.059: CSV に処理結果を書き出す ===

書き込み完了: /var/folders/3y/fmw40k0x78xblvb3gkcyvy1h0000gn/T/kobo_quality_summary.csv

=== 書き出したCSVの内容(csv.reader で確認)===
  ヘッダー: ['ライン', '製品コード', '製品名', '生産数合計', '不良数合計', '不良率', '良品数合計']
  行 [ 1]: ['Aライン', 'EG-1001', 'エンジン部品A', '51690', '978', '1.89', '50712']
  行 [ 2]: ['Bライン', 'EG-1002', 'エンジン部品B', '43727', '1185', '2.71', '42542']
  行 [ 3]: ['Cライン', 'HN-2001', 'ハーネスユニット', '73157', '2817', '3.85', '70340']
  行 [ 4]: ['Dライン', 'SN-3001', 'センサーモジュール', '57698', '1253', '2.17', '56445']
  行 [ 5]: ['Eライン', 'FR-4001', 'フレーム部品', '38546', '497', '1.29', '38049']
  行 [ 6]: ['全ライン合計', '', '', '264818', '6730', '2.54', '258088']

結果の読み取り

csv.writer でヘッダー行・5ラインのデータ行・全体合計行の計 7 行が CSV ファイルに書き出されました。
書き出した CSV を再度 csv.reader で読み込むことで、
ヘッダーとデータが正しくリスト構造になっていることが確認できます。
writer.writerow() の引数はリストなので、
[line, t["コード"], ...] という形で各列の値を並べるだけで CSV が完成します。
この出力 CSV は Excel でそのまま開いて確認・共有できます。


No.060:売上CSVから月別売上を集計する

実務での意味

売上 CSV から 月別・製品別の売上を集計してグラフ化 するのは、
月次経営報告・製品ライン管理の核心業務です。
csv.DictReader を使うと列名でアクセスできるため、
列インデックスを意識せずに読みやすいコードが書けます。

分析・モデル化の考え方

csv.DictReader(f) は各行を {列名: 値} の辞書として返します。
row["金額"] のように 列名で直接アクセス できるため可読性が高く、
CSV の列順が変わっても影響を受けません。
月別集計には row["日付"][:7]"2025-01" 形式の月キーを取り出して辞書に集計します。

Python で確認する

# No.060: 売上 CSV から月別・製品別売上を集計する(csv.DictReader)
print("=== No.060: 売上 CSV から月別売上を集計する ===")
print()

monthly_amounts = defaultdict(int)  # {月キー: 金額合計}
monthly_qty = defaultdict(int)  # {月キー: 数量合計}
code_monthly_060 = defaultdict(lambda: defaultdict(int))  # {製品コード: {月キー: 金額合計}}

with open(SALES_CSV, "r", encoding="utf-8", newline="") as f:
    reader = csv.DictReader(f)  # 列名で直接アクセス可能
    for row in reader:
        month_key = row["日付"][:7]  # "2025-01-06" -> "2025-01"
        amount = int(row["金額"])
        qty = int(row["数量"])
        code = row["製品コード"]
        monthly_amounts[month_key] += amount
        monthly_qty[month_key] += qty
        code_monthly_060[code][month_key] += amount

months_060 = sorted(monthly_amounts.keys())

print("=== 月別売上集計 ===")
print(f"{'月':<10} {'売上金額':>16} {'数量合計':>10}")
print("-" * 40)
for m in months_060:
    print(f"{m:<10} {monthly_amounts[m]:>16,}{monthly_qty[m]:>8,} 個")
print("-" * 40)
grand_total_060 = sum(monthly_amounts.values())
print(f"{'3ヶ月合計':<10} {grand_total_060:>16,} 円")

print()

# 製品コード別 月次売上
product_codes_060 = sorted(code_monthly_060.keys())
pname_map = {lc["code"]: lc["name"] for lc in lines_config}
print("=== 製品コード別 月次売上 ===")
header_row = f"{'製品コード':<10} {'製品名':<18}" + "".join(f" {m:>14}" for m in months_060) + "      合計"
print(header_row)
print("-" * (len(header_row) + 4))
for code in product_codes_060:
    row_vals = [code_monthly_060[code].get(m, 0) for m in months_060]
    row_str = f"{code:<10} {pname_map.get(code,''):<18}"
    for v in row_vals:
        row_str += f" {v:>12,}円"
    row_str += f" {sum(row_vals):>12,}円"
    print(row_str)
=== No.060: 売上 CSV から月別売上を集計する ===

=== 月別売上集計 ===
月                      売上金額       数量合計
----------------------------------------
2025-01         279,739,400 円   85,438 個
2025-02         287,619,600 円   88,148 個
2025-03         293,686,100 円   89,661 個
----------------------------------------
3ヶ月合計           861,045,100 円

=== 製品コード別 月次売上 ===
製品コード      製品名                       2025-01        2025-02        2025-03      合計
--------------------------------------------------------------------------------------
EG-1001    エンジン部品A              70,606,200円   73,323,600円   73,806,600円  217,736,400円
EG-1002    エンジン部品B              53,751,000円   55,567,400円   56,886,000円  166,204,400円
FR-4001    フレーム部品               67,919,500円   68,442,000円   71,230,500円  207,592,000円
HN-2001    ハーネスユニット             35,203,500円   36,759,000円   36,813,000円  108,775,500円
SN-3001    センサーモジュール            52,259,200円   53,527,600円   54,950,000円  160,736,800円
# No.060: 月別売上グラフ
colors_060 = ["#2E86AB", "#A23B72", "#F18F01", "#C73E1D", "#44BBA4"]

fig, axes = plt.subplots(1, 2, figsize=(13, 5))
x_pos_060 = list(range(len(months_060)))

# 左: 月別総売上(棒グラフ)
month_amounts_万 = [monthly_amounts[m] / 10_000 for m in months_060]
bars_060 = axes[0].bar(x_pos_060, month_amounts_万, color=["#2E86AB", "#A23B72", "#F18F01"], alpha=0.82, width=0.5)
for bar, val in zip(bars_060, month_amounts_万):
    axes[0].text(
        bar.get_x() + bar.get_width() / 2,
        bar.get_height() + 30,
        f"{val:,.0f}万",
        ha="center",
        va="bottom",
        fontsize=10,
        fontweight="bold",
    )
axes[0].set_title("月別 総売上", fontsize=13, pad=10)
axes[0].set_xlabel("月", fontsize=11)
axes[0].set_ylabel("売上金額(万円)", fontsize=11)
axes[0].set_xticks(x_pos_060)
axes[0].set_xticklabels(months_060, fontsize=10)
axes[0].yaxis.set_major_formatter(ticker.FuncFormatter(lambda v, _: f"{v:,.0f}"))
axes[0].grid(axis="y", alpha=0.3)

# 右: 製品別月別売上(積み上げ棒グラフ)
bottom_060 = [0.0] * len(months_060)
for i, code in enumerate(product_codes_060):
    vals = [code_monthly_060[code].get(m, 0) / 10_000 for m in months_060]
    axes[1].bar(x_pos_060, vals, bottom=bottom_060, color=colors_060[i], alpha=0.85, label=pname_map.get(code, code))
    bottom_060 = [b + v for b, v in zip(bottom_060, vals)]

axes[1].set_title("製品別 月別売上(積み上げ)", fontsize=13, pad=10)
axes[1].set_xlabel("月", fontsize=11)
axes[1].set_ylabel("売上金額(万円)", fontsize=11)
axes[1].set_xticks(x_pos_060)
axes[1].set_xticklabels(months_060, fontsize=10)
axes[1].yaxis.set_major_formatter(ticker.FuncFormatter(lambda v, _: f"{v:,.0f}"))
axes[1].legend(fontsize=9, loc="upper left")
axes[1].grid(axis="y", alpha=0.3)

plt.tight_layout()
plt.show()
findfont: Failed to find font weight bold, now using 400.


svg

結果の読み取り

3 ヶ月分の売上 CSV から月別・製品コード別の売上が集計されました。
月別総売上は 1月 279,739,400 円 → 2月 287,619,600 円 → 3月 293,686,100 円
緩やかに増加しており、3 ヶ月合計は 861,045,100 円 です。
製品別では エンジン部品 A(EG-1001)が 217,736,400 円 で最大、
積み上げグラフではフレーム部品(FR-4001: 単価 5,500 円)が高単価で大きな面積を占めています。
csv.DictReaderrow["金額"] という列名アクセスと
row["日付"][:7] でスライスして月キーを作るパターンは月次集計の定番テクニックです。


対象ノックを通して見える実務上の示唆

No.051〜060 で学んだファイル操作と CSV 処理は、製造データ分析の 入出力の基盤 です。

1. with 文を常に使う

with open(...) as f: は単なるスタイルではなく 安全性の確保 です。
f.close() を書き忘れた場合のファイルロックや、例外時のハンドル漏れを防ぎます。
製造データの本番処理では with 文を使わない open() は書かないことを習慣にしましょう。

2. CSV の値は文字列型であることを意識する

csv.reader が返す値はすべて文字列です。
int(row[4])float(row[6]) の変換を忘れると、
'850' + '720' = '850720'(文字列結合)という計算誤りが発生します。
変換エラーへの対処は第5章(No.049 try-except)で学んだパターンを組み合わせます。

3. 列インデックスを定数化する

IDX_PROD = 4 という定数定義により、CSV の列構成が変わっても
修正箇所が 1 か所で済みます。
さらに csv.DictReader を使えば列名でアクセスできて保守性が高まります。

4. csv.writer で集計結果を次工程に渡す

Python で集計した結果を CSV に書き出すことで、
Excel・BIツール・ERP との連携が実現できます。
「Python で集計 → CSV で書き出し → Excel で確認」という流れが
製造現場の DX 推進における最初のステップです。

実務導入する場合に必要なこと

Step 1: 既存 CSV のフォーマット確認(約半日)

現場の検査端末や ERP から出力される CSV の
「文字コード・列構成・区切り文字・日付フォーマット」を確認します。

確認項目よくある問題対処法
文字コードShift-JIS で出力されるencoding='cp932' を指定
区切り文字タブ区切り(TSV)の場合csv.reader(f, delimiter='\t')
日付フォーマット20250106(8桁整数)形式datetime.strptime(d, '%Y%m%d') で変換
数値のカンマ'1,200' 形式の数値int(row[4].replace(',', ''))

Step 2: 読み込み→集計→書き出しパイプラインの構築(約1日)

open() で CSV 読み込み
  → csv.reader / DictReader で行単位処理
  → 条件分岐・集計(第4章/第5章のスキル)
  → csv.writer で集計結果を書き出し

Step 3: 定期実行の自動化(約半日)

cron(Linux/macOS)や タスクスケジューラ(Windows)で
スクリプトを定期実行し、毎日 CSV 集計が自動化されます。
第5章(No.046〜050)の datetime モジュールと組み合わせると
「今日の日付のフォルダから CSV を読み込む」処理が実装できます。

Step 4: 第8章(Polars)への移行判断

CSV が数十万行以上になったり、複雑な集計が必要になった場合は
第8章で学ぶ Polarsread_csv() / group_by() への移行を検討します。
本章の csv モジュールは Python 標準ライブラリで依存なし・小規模向き、
Polars は高速・大規模データ向きと使い分けます。

まとめ

本章(No.051〜060)で学んだ内容を整理します。

No.スキル製造現場での活用
051ファイルを読み込む生産日報テキストを open() で読み込む
052ファイルに書き込む品質アラートレポートをテキストファイルに書き出す
053with文でファイルを安全に扱うライン集計サマリーを with open() で安全に読み書き
054CSVファイルの構造を理解する品質検査 CSV の列構成・データ型を生の文字列で確認
055csvモジュールでCSVを読み込むcsv.reader でヘッダー・データを適切に読み込む
056CSVの行を1行ずつ処理する全行走査で不良率アラート件数を集計する
057CSVから特定の列を取り出す列インデックス定数で製品コード・生産数を抽出
058CSVの数値列を合計するライン別累計生産数・不良数を集計・可視化
059CSVに処理結果を書き出すcsv.writer で集計サマリーを書き出す
060売上CSVから月別売上を集計するDictReader で月別・製品別売上を集計しグラフ化

第7章 では NumPy を使って、本章の CSV 集計処理を
配列演算で高速化する方法を学びます。
np.sum() / np.mean() などの関数は、for ループよりも
大量データの集計を高速かつ簡潔に記述できます。

法人向けのご相談


数理工房 では、製造業・DX 推進担当者向けの Python 研修・データ分析支援を提供しています。

こんなお悩みはありませんか?

  • 「検査端末・ERP から出力される CSV を Python で自動集計したい」
  • 「毎日手作業でコピペしている品質レポートを自動生成したい」
  • 「Python で集計した結果を Excel・BIシステムと連携させたい」
  • 「社内向けにカスタマイズした Python 研修プログラムを作ってほしい」

提供サービス

サービス概要
製造業向け Python 研修現場データを使った実践的な研修(オンライン/対面)
CSV 自動集計システム構築検査 CSV・売上 CSV の自動集計・レポート生成
品質管理自動化支援不良率アラート・品質レポート自動化
DX 推進コンサルティング課題整理から実装まで一貫支援

📩 お問い合わせ: surikobo.co.jp/contact
まずはお気軽にご相談ください。