PythonでExcelの大ファイルを「チャンク風」に分割処理する入門 — 迂回戦略での分割読み
まず事実から。pandas.read_excel は chunksize や iterator をサポートしていません。つまり、CSVのように「ネイティブなストリーミング読み込み」はできません。ですが、スキップと部分読み込みを組み合わせることで「チャンク風」に処理できます。初心者向けに、安全で現実的なやり方をコードテンプレートでまとめます。
基本方針と現実的な選択肢
- Excelを直接チャンク読みは不可:
read_excel(iterator=True, chunksize=...)は使えません。 - 現実的な迂回策:
skiprows+nrowsをループして「ページング」する(Excelのまま)。
- 先に CSV に変換し、
read_csv(chunksize=N)で真のチャンク処理。
- エンジンの読み取りモード(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つまずきやすいポイントと回避策
- 列名行がどこか曖昧: 先に少量読み込みして構造を確定し、
headerとusecolsを固定してからチャンク処理に入る。 - 先頭ゼロが消えるコード列:
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_excelはchunksize・iterator非対応。Excelを直接「チャンク読み」するには、skiprows+nrowsのループで擬似的に行う。- 本格的なストリーミングが必要なら、CSVに変換して
read_csv(chunksize=...)を使うのが最適解。 - openpyxlの
read_only=Trueで逐次行処理も可能。バッファを区切って DataFrame 化し、分割集計を回す。
