Python Excel操作 逆引き集 | チャンクで読み込む(大ファイル)

Python
スポンサーリンク

PythonでExcelの大ファイルを「チャンク風」に分割処理する入門 — 迂回戦略での分割読み

まず事実から。pandas.read_excelchunksizeiterator をサポートしていません。つまり、CSVのように「ネイティブなストリーミング読み込み」はできません。ですが、スキップと部分読み込みを組み合わせることで「チャンク風」に処理できます。初心者向けに、安全で現実的なやり方をコードテンプレートでまとめます。


基本方針と現実的な選択肢

  • Excelを直接チャンク読みは不可: read_excel(iterator=True, chunksize=...) は使えません。
  • 現実的な迂回策:
      1. skiprows + nrows をループして「ページング」する(Excelのまま)。
      1. 先に CSV に変換し、read_csv(chunksize=N) で真のチャンク処理。
      1. エンジンの読み取りモード(openpyxlの read_only=True)で逐次行イテレーションし、手動でバッファに詰める。

用途に応じて最短コードが変わります。以下で具体例を示します。


テンプレート1:skiprows + nrows で「チャンク風」読み(Excelのまま)

単一シートを固定サイズで順番に処理

import pandas as pd

FILE = "big.xlsx"
SHEET = "Sheet1"
CHUNK = 10000  # 1万行ずつ

start = 0
while True:
    df = pd.read_excel(
        FILE,
        sheet_name=SHEET,
        skiprows=range(start),  # 先頭 start 行を飛ばす
        nrows=CHUNK,            # 次の CHUNK 行だけ読む
        header=0                # 列名行が1行目にある前提(必要に応じて調整)
    )
    if df.empty:
        break

    # ここでチャンクを処理(例:集計)
    df["Amount"] = pd.to_numeric(df.get("Amount"), errors="coerce")
    chunk_sum = df["Amount"].sum()
    print(f"{start}{start+len(df)-1} 行の合計: {chunk_sum}")

    # 次チャンクへ
    start += len(df)
Python
  • コツ: 列名・開始行がズレる帳票では、最初に列名行を特定してから header を合わせます。
  • メリット: 追加ライブラリ不要。巨大ファイルでも「段階処理」が可能。
  • デメリット: 各チャンクでExcelを再パースするため、CSVより重い。

テンプレート2:CSV化してから本格チャンク処理(推奨)

Excel→CSV→read_csv(chunksize) の王道ルート

import pandas as pd

# 1) まず必要列だけでCSV化(メモリが許せば一度だけ)
df_head = pd.read_excel("big.xlsx", sheet_name="Sheet1", usecols=["Date", "Product", "Amount"])
df_head.to_csv("big_sheet1.csv", index=False)

# 2) 本格チャンク処理(真のストリーミング)
total = 0
for chunk in pd.read_csv("big_sheet1.csv", chunksize=10000, parse_dates=["Date"]):
    chunk["Amount"] = pd.to_numeric(chunk["Amount"], errors="coerce")
    total += chunk["Amount"].sum()

print("総合計:", total)
Python
  • 利点: read_csv(chunksize=...) は本物のイテレータ。メモリ効率・速度に優れます。
  • さらに効率化: Excel→CSV は一度だけ実施し、以後は CSV で運用。

テンプレート3:openpyxl の read_only でセルを逐次走査(高度)

行をストリームで読み、バッファが溜まったらDataFrame化して処理

from openpyxl import load_workbook
import pandas as pd

FILE = "big.xlsx"
SHEET = "Sheet1"
CHUNK = 10000

wb = load_workbook(FILE, read_only=True, data_only=True)  # 式は値で
ws = wb[SHEET]

rows_iter = ws.iter_rows(values_only=True)

# 1行目を列名として取得(帳票に応じて調整)
columns = next(rows_iter)

buffer = []
processed = 0

for row in rows_iter:
    buffer.append(row)
    if len(buffer) >= CHUNK:
        df = pd.DataFrame(buffer, columns=columns)
        # ここで処理
        df["Amount"] = pd.to_numeric(df.get("Amount"), errors="coerce")
        print("チャンク合計:", df["Amount"].sum())
        processed += len(df)
        buffer.clear()

# 残りのバッファを処理
if buffer:
    df = pd.DataFrame(buffer, columns=columns)
    df["Amount"] = pd.to_numeric(df.get("Amount"), errors="coerce")
    print("最後のチャンク合計:", df["Amount"].sum())
    processed += len(df)

print("処理行数:", processed)
wb.close()
Python
  • メリット: 真のストリーム読み(巨大ファイルに強い)。
  • 注意: 画像・結合セル・複雑なフォーマットは扱いにくい。整形は自前で。

応用パターン

複数シートを順番にチャンク処理(skiprows+nrows版)

import pandas as pd

FILE = "book.xlsx"
SHEETS = ["Q1", "Q2", "Q3", "Q4"]
CHUNK = 5000

for s in SHEETS:
    start = 0
    print(f"=== {s} ===")
    while True:
        df = pd.read_excel(FILE, sheet_name=s, skiprows=range(start), nrows=CHUNK, header=0, usecols=["Date","Revenue"])
        if df.empty: break
        df["Revenue"] = pd.to_numeric(df["Revenue"], errors="coerce")
        print(f"{start}{start+len(df)-1} 行の合計:", df["Revenue"].sum())
        start += len(df)
Python

チャンクごとに中間結果を保存(メモリ節約)

import pandas as pd
import pathlib

out = pathlib.Path("out")
out.mkdir(exist_ok=True)

start = 0
idx = 0
while True:
    df = pd.read_excel("big.xlsx", skiprows=range(start), nrows=10000, header=0, usecols=["Code","Amount"])
    if df.empty: break
    df["Amount"] = pd.to_numeric(df["Amount"], errors="coerce")
    df.to_parquet(out / f"chunk_{idx:03d}.parquet", index=False)  # 高速・圧縮
    start += len(df); idx += 1
Python

つまずきやすいポイントと回避策

  • 列名行がどこか曖昧: 先に少量読み込みして構造を確定し、headerusecols を固定してからチャンク処理に入る。
  • 先頭ゼロが消えるコード列: dtype={"Code": "string"} を最初の小規模読みで確認→チャンク処理時も同じ前提で扱う(必要なら後処理で astype("string"))。
  • 日付の混在: チャンク内で pd.to_datetime(..., errors="coerce") を都度適用。集計の直前に .dt.to_period("M") などへ。
  • 速度が出ない: Excelパースは重い。可能なら CSV へ一度変換→read_csv(chunksize) を使う。
  • メモリ膨張: チャンクサイズを下げる、不要列は usecols で削る、中間は Parquet に逃がす。

ミニ例題(練習用)

  • 例題1:Excelをチャンク風に合計
import pandas as pd
start = 0; total = 0
while True:
    df = pd.read_excel("sales.xlsx", skiprows=range(start), nrows=20000, header=0, usecols=["Amount"])
    if df.empty: break
    df["Amount"] = pd.to_numeric(df["Amount"], errors="coerce")
    total += df["Amount"].sum()
    start += len(df)
print("総合計:", total)
Python
  • 例題2:CSV化してから真のチャンク処理
import pandas as pd
pd.read_excel("sales.xlsx", usecols=["Date","Amount"]).to_csv("sales.csv", index=False)
total = 0
for chunk in pd.read_csv("sales.csv", chunksize=50000, parse_dates=["Date"]):
    total += pd.to_numeric(chunk["Amount"], errors="coerce").sum()
print("総合計:", total)
Python
  • 例題3:openpyxlでストリーミング読み+月次集計
from openpyxl import load_workbook
import pandas as pd
wb = load_workbook("sales.xlsx", read_only=True, data_only=True)
ws = wb["Sheet1"]
rows = ws.iter_rows(values_only=True)
cols = next(rows)
buf = []; monthly = {}
for r in rows:
    buf.append(r)
    if len(buf) >= 20000:
        df = pd.DataFrame(buf, columns=cols)
        df["Date"] = pd.to_datetime(df["Date"], errors="coerce")
        df["Amount"] = pd.to_numeric(df["Amount"], errors="coerce")
        for k, v in df.assign(m=df["Date"].dt.to_period("M")).groupby("m")["Amount"].sum().items():
            monthly[k] = monthly.get(k, 0) + v
        buf.clear()
# 残りも処理(省略可)
print(monthly)
wb.close()
Python

まとめ

  • read_excelchunksizeiterator 非対応。Excelを直接「チャンク読み」するには、skiprows+nrowsのループで擬似的に行う。
  • 本格的なストリーミングが必要なら、CSVに変換して read_csv(chunksize=...) を使うのが最適解。
  • openpyxlの read_only=True で逐次行処理も可能。バッファを区切って DataFrame 化し、分割集計を回す。

タイトルとURLをコピーしました