0Pricing
Pandas & NumPy Academy · レッスン

大容量ファイルのチャンク読み込み

read_csvのchunksizeを使ってチャンク単位で読み込み、RAMを超えるファイルを処理しながら結果を段階的に集計します。

「大容量ファイルのチャンク読み込み」はCoddyKit上の無料Pandas & NumPy Academyレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはPandas & NumPy Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Pandas & NumPy Academyコースには全4レッスンが含まれています。

ファイルが RAM を超える場合

本番環境のデータパイプラインでよくあるボトルネックの1つが、利用可能な RAM より大きい CSV ファイルです。ファイルが20 GBで、マシンの RAM が16 GBしかない場合、pd.read_csv('file.csv') は MemoryError で失敗します。解決策はチャンク単位の読み込みです。ファイルを固定サイズの単位に分けて読み込み、それぞれを処理して結果を蓄積します。これにより、ファイル全体をメモリに読み込まずに、任意の大きさのファイルを分析できます。

import pandas as pd
import numpy as np

# Simulate a large CSV by writing one
np.random.seed(0)
sample = pd.DataFrame({
    'date': pd.date_range('2023-01-01', periods=100000, freq='h'),
    'region': np.random.choice(['North','South','East','West'], 100000),
    'sales': np.random.randint(100, 1000, 100000)
})
sample.to_csv('/tmp/large_sales.csv', index=False)
print(f'File size: {pd.io.common.get_handle("/tmp/large_sales.csv", "r").handle.seek(0, 2)/1e6:.1f} MB... (simulated)')
print('Rows:', len(sample))

read_csv の chunksize パラメータ

pd.read_csv() に chunksize=n を渡すと、DataFrame ではなく TextFileReader iterator が返されます。各反復では、次の n 行が DataFrame として返されます。この方法では、一度にメモリ上に保持されるのは n 行だけです。chunksize の開始値としては、100,000行が適しています。RAM に収まり、I/O のオーバーヘッドも抑えられる大きさです。利用可能な RAM と列数に応じて調整してください。

import pandas as pd

# Read file in chunks of 25,000 rows
chunk_iter = pd.read_csv('/tmp/large_sales.csv', chunksize=25000)

# Peek at the first chunk
first_chunk = next(chunk_iter)
print('First chunk shape:', first_chunk.shape)
print(first_chunk.head(3))

チャンクの反復処理

チャンクの iterator に対して for ループを使用し、各チャンクを処理します。行数のカウント、列の合計、フィルタリングなどの単純な処理では、各チャンクを個別に処理し、結果をリストまたは累積値に追加します。iterator は一度消費すると再開できないため、必ず with 文の中で使用するか、パイプラインを実行するたびに作り直してください。

import pandas as pd

total_rows = 0
total_sales = 0.0
chunk_count = 0

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    total_rows += len(chunk)
    total_sales += chunk['sales'].sum()
    chunk_count += 1

print(f'Chunks processed: {chunk_count}')
print(f'Total rows: {total_rows:,}')
print(f'Total sales: {total_sales:,.0f}')
print(f'Avg sales per row: {total_sales/total_rows:.2f}')

チャンク処理中の行のフィルタリング

不要なデータが蓄積する前に破棄できるよう、ループ内で行のフィルターを適用します。条件に一致する行だけを保持するため、メモリ使用量を低く抑えられます。たとえば、10 GBのファイルから 'North' 地域の行をすべて抽出する場合は、結果リストに追加する前に各チャンクをフィルタリングします。最後に実行する pd.concat が扱うのは、はるかに小さいフィルタリング済みのデータだけになります。

import pandas as pd

filtered_chunks = []

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Keep only North region rows
    north = chunk[chunk['region'] == 'North']
    if len(north) > 0:
        filtered_chunks.append(north)

north_df = pd.concat(filtered_chunks, ignore_index=True)
print(f'North region rows: {len(north_df):,}')
print(f'North total sales: {north_df["sales"].sum():,.0f}')

GroupBy の逐次集計

チャンクをまたいだ GroupBy 集計は、単純な合計よりも難しくなります。これは、同じグループが複数のチャンクにまたがる可能性があるためです。基本的な手順は、各チャンクでグループごとの合計と件数を計算し、それらの部分結果を連結して、蓄積した部分結果に対して最後に groupby を実行することです。各チャンクで groupby().mean() を実行してから平均値同士を平均してはいけません。チャンク間でグループのサイズが異なる場合、誤った結果になるためです。

import pandas as pd

partials = []

for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Compute sum and count per region in this chunk
    partial = chunk.groupby('region')['sales'].agg(['sum', 'count'])
    partials.append(partial)

# Combine all partial results
combined = pd.concat(partials).groupby(level=0).sum()
combined['mean'] = combined['sum'] / combined['count']

print('Regional aggregation (chunked):')
print(combined.round(2))

読み込み時に指定するメモリ効率のよい型

read_csv で列の dtypes をあらかじめ指定すると、チャンク読み込み中のメモリ使用量を削減できます。これにより、Pandas が各チャンクに対して幅の広い型を割り当て、その後に破棄することを防げます。dtype={'region': 'category', 'sales': 'int32'} のような辞書を read_csv() に渡します。チャンク単位の読み込みと組み合わせると、単純にファイル全体を読み込む場合と比べて、ピーク時のメモリ使用量を10%未満に削減できることがあります。

import pandas as pd

specified_dtypes = {
    'region': 'category',
    'sales': 'int32'
}

total_sales = 0
for chunk in pd.read_csv(
    '/tmp/large_sales.csv',
    chunksize=25000,
    dtype=specified_dtypes,
    parse_dates=['date']
):
    total_sales += chunk['sales'].sum()
    # Only 25k rows * (category + int32 + datetime) in RAM at once

print(f'Total sales (memory-efficient): {total_sales:,.0f}')

結果を逐次書き込む

各チャンクの処理結果もファイルに書き込む必要がある場合は、すべてをメモリに蓄積せず、処理したチャンクを順次書き込みます。to_csv() で mode='a'(追記)を使用し、最初のチャンク以降は header=False を指定します。これにより、入力と出力のメモリ使用量をどちらもチャンクサイズに抑えられるため、メモリが限られたマシンでも任意の大きさのファイルを処理できるパイプラインになります。

import pandas as pd
import os

output_path = '/tmp/filtered_output.csv'

# Remove previous output if it exists
if os.path.exists(output_path):
    os.remove(output_path)

first_chunk = True
for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=25000):
    # Process: keep only high-value rows
    processed = chunk[chunk['sales'] > 800].copy()
    
    # Write: header only on first chunk, append thereafter
    processed.to_csv(output_path,
                     mode='a',
                     header=first_chunk,
                     index=False)
    first_chunk = False

result = pd.read_csv(output_path)
print(f'High-value rows written: {len(result):,}')
print(f'Average sales (>800): {result["sales"].mean():.1f}')

ドロップインの代替手段としての Dask の紹介

Dask は Pandas に似た API を提供し、チャンク全体に対する処理を遅延実行かつ並列で自動的に行います。手動でチャンク処理のループを書く代わりに、dask_df = dd.read_csv('file.csv') と記述し、その後は groupby、filter、merge など、Pandas と同じ操作を記述します。最後に .compute() を呼び出して実行を開始します。チャンク処理で手動実装するのが難しい複雑な複数段階の処理をパイプラインに含める場合、Dask は最も実用的な解決策です。

# pip install dask
# import dask.dataframe as dd

# Read the large CSV as a Dask DataFrame (lazy — no data loaded yet)
# ddf = dd.read_csv('/tmp/large_sales.csv')

# Operations are lazy — no computation happens until .compute()
# result = ddf.groupby('region')['sales'].mean().compute()
# print(result)

# Key Dask advantages:
# - Automatic chunking (no manual chunksize loops)
# - Parallel execution (multi-core)
# - Familiar Pandas API
# - Works with files larger than RAM

print('Dask extends Pandas to datasets larger than RAM with minimal API changes.')

チャンクサイズの選択

理想的なチャンクサイズは、相反する2つの要素のバランスで決まります。小さすぎる場合(例:1,000行)は、チャンクごとのオーバーヘッド(ファイル I/O の準備や DataFrame の作成)が大きくなり、ループに時間がかかります。大きすぎる場合(例:1,000万行)は、一度に RAM に読み込むデータが多くなり、チャンク処理の目的が失われます。実用的な開始点として、メモリ上で50~200 MBになるチャンクを目標にします。各列が平均8バイトの10列の DataFrame では、100,000行で1チャンクあたり約8 MBとなり、十分な余裕を残せる安全なデフォルト値です。

import pandas as pd
import timeit

# Benchmark different chunk sizes
for chunksize in [10000, 50000, 100000]:
    t = timeit.timeit(
        lambda: sum(chunk['sales'].sum()
                    for chunk in pd.read_csv('/tmp/large_sales.csv', chunksize=chunksize)),
        number=3
    )
    print(f'chunksize={chunksize:>7,}: {t/3:.3f}s per pass')

大規模データにおける Parquet と CSV の比較

ファイル形式を選べる場合は、大規模なデータセットには CSV よりも Parquet を使用します。Parquet は列指向のバイナリ形式で、必要な列だけを読み込める(列プルーニング)、CSV よりも効率よく圧縮できる、dtype を保持できる(読み込み時に再推論する必要がない)、同等の CSV より5~20倍高速に読み込めるという特徴があります。大きな CSV は、pd.read_csv('file.csv', chunksize=500000) と to_parquet を使って一度だけ Parquet に変換し、その後の読み込みでは pd.read_parquet(columns=['col1','col2']) を使用します。

import pandas as pd

# Convert our CSV to Parquet (do this once)
df = pd.read_csv('/tmp/large_sales.csv', parse_dates=['date'])
df['region'] = df['region'].astype('category')
df['sales'] = df['sales'].astype('int32')
df.to_parquet('/tmp/large_sales.parquet', index=False)

# Now read only the columns needed — much faster than CSV
sales_by_region = pd.read_parquet(
    '/tmp/large_sales.parquet',
    columns=['region', 'sales']
)
print('Parquet read result:')
print(sales_by_region.groupby('region')['sales'].mean().round(1))
print('\ndtypes preserved:', sales_by_region.dtypes.to_dict())

チャンク処理パイプラインの全体パターン

大きなファイルを処理する完全なパターンは次のとおりです。1) 読み込み時に dtype を指定します。2) ループ内でできるだけ早い段階で行をフィルタリングします。3) チャンクごとに部分集計を計算します(合計と件数を使い、平均を直接計算しません)。4) ループの後で部分結果を結合し、最終的な統計量を計算します。5) 結果が大きい場合は、出力を逐次書き込みます。このパターンは、chunksize を適切に調整すれば、どのようなマシンでも任意のサイズのファイルを処理できます。

理解度チェック

このレッスンで学んだチャンク単位の読み込みについて、理解度を確認します。

レッスンのまとめ

このレッスンでは、read_csv の chunksize によってチャンクごとに1つの DataFrame を返す iterator が得られ、RAM より大きなファイルを逐次処理できること、部分集計(合計と件数)によってチャンク間でも正しく値を累積できる一方、平均は直接平均できないこと、そして圧縮、速度、dtype の保持という点で、Parquet 形式が大規模データセットに対する CSV の長期的な代替手段として最適であることを学びました。これで Pandas パフォーマンスのヒントのコースは完了です。次の上級 B2 コースに進む準備ができました。

よくある質問

「大容量ファイルのチャンク読み込み」レッスンは無料ですか?

はい。「大容量ファイルのチャンク読み込み」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Pandas & NumPy Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Pandas & NumPy Academyコースには全4レッスンが含まれています。

「大容量ファイルのチャンク読み込み」で何を学びますか?

read_csvのchunksizeを使ってチャンク単位で読み込み、RAMを超えるファイルを処理しながら結果を段階的に集計します。 ブラウザで直接実行するハンズオンコードでPandas & NumPy Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Pandas & NumPy Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのPandas & NumPy Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「大容量ファイルのチャンク読み込み」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このPandas & NumPy Academyレッスンでコードを書いて実行できますか?

はい。すべてのPandas & NumPy Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. timeitとmemory_profilerによるプロファイリング
  2. iterrowsとPythonループの回避
  3. メモリ削減のための効率的なデータ型
  4. 大容量ファイルのチャンク読み込み
← Pandas & NumPy Academyに戻る