0Pricing
Pandas & NumPy Academy · Lesson

Chunked Reading for Large Files

Process files that exceed RAM by reading in chunks with chunksize in read_csv and aggregating results incrementally.

Chunked Reading for Large Files is a free Pandas & NumPy Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Pandas & NumPy Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

When Files Exceed RAM

A common bottleneck in production data pipelines is a CSV file that is larger than available RAM. If a file is 20 GB and the machine has 16 GB of RAM, pd.read_csv('file.csv') will crash with a MemoryError. The solution is chunked reading: loading the file in fixed-size pieces, processing each piece, and accumulating results. This allows you to analyse arbitrarily large files without loading them entirely into memory.

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))

chunksize Parameter in read_csv

Pass chunksize=n to pd.read_csv() to return a TextFileReader iterator instead of a DataFrame. Each iteration yields the next n rows as a DataFrame. This way, only n rows are in memory at a time. A good starting value for chunksize is 100,000 rows — small enough to fit in RAM but large enough to keep I/O overhead manageable. Adjust based on your available RAM and column count.

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))

Iterating Over Chunks

Use a for loop over the chunk iterator to process each chunk. For simple operations like counting rows, summing a column, or filtering, process each chunk independently and accumulate the results in a list or running total. Always call the iterator inside a with statement or ensure you re-create it for each pipeline run — iterators are consumed once and cannot be restarted.

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}')

Filtering Rows While Chunking

Apply row filters inside the loop to discard unwanted data before it accumulates. This keeps memory usage low by only retaining rows that match your criteria. For example, to extract all 'North' region rows from a 10 GB file, filter each chunk before appending to your results list. The final pd.concat only handles the filtered subset, which is much smaller.

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}')

Incremental GroupBy Aggregation

GroupBy aggregations across chunks are trickier than simple sums because groups may span multiple chunks. The pattern is: compute group-level sums and counts per chunk, concatenate these partial results, and perform a final groupby on the accumulated partial results. Never call groupby().mean() per chunk and then average the averages — this gives wrong results when group sizes differ across chunks.

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))

Memory-Efficient Type Specification at Load Time

Reduce memory during chunked loading by specifying column dtypes upfront in read_csv. This prevents Pandas from allocating wide types for each chunk and then discarding them. Pass a dict like dtype={'region': 'category', 'sales': 'int32'} to read_csv(). Combined with chunked reading, this can reduce peak memory to less than 10% of a naive full-file load.

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}')

Writing Results Incrementally

When the output of processing each chunk also needs to be written to a file, write each processed chunk as you go rather than accumulating everything in memory. Use mode='a' (append) and header=False (after the first chunk) with to_csv(). This keeps both input and output memory footprints at chunk size, making the pipeline capable of processing arbitrarily large files on a memory-constrained machine.

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}')

Introduction to Dask as a Drop-In Alternative

Dask provides a Pandas-like API that executes operations lazily and in parallel across chunks automatically. Instead of writing a manual chunking loop, you write dask_df = dd.read_csv('file.csv') and then the same Pandas operations — groupby, filter, merge. Call .compute() at the end to trigger execution. Dask is the most practical solution when your pipeline involves complex multi-step operations that are hard to implement manually with chunking.

# 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.')

Choosing Chunk Size

The ideal chunk size balances two competing factors: too small (e.g. 1,000 rows) causes high per-chunk overhead (file I/O setup, DataFrame creation) and the loop takes longer. Too large (e.g. 10M rows) defeats the purpose by loading too much data into RAM at once. A practical starting point: target chunks of 50-200 MB in memory. For a DataFrame with 10 columns averaging 8 bytes each, 100,000 rows ≈ 8 MB per chunk — a safe default that leaves plenty of headroom.

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 vs CSV for Large Data

If you control the file format, prefer Parquet over CSV for large datasets. Parquet is a columnar binary format that: loads only requested columns (column pruning), compresses much better than CSV, preserves dtypes (no re-inference on load), and reads 5-20x faster than an equivalent CSV. Convert a large CSV to Parquet once with pd.read_csv('file.csv', chunksize=500000) + to_parquet, then use pd.read_parquet(columns=['col1','col2']) for all subsequent reads.

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())

Full Chunked Pipeline Pattern

A complete pattern for large file processing: 1) Specify dtypes at read time. 2) Filter rows as early as possible inside the loop. 3) Compute partial aggregates per chunk (sum + count, never mean directly). 4) After the loop, combine partials and compute final statistics. 5) Write output incrementally if results are large. This pattern handles files of any size on any machine with adequate chunksize tuning.

Quick Check

Test your understanding of chunked reading from this lesson.

Lesson Recap

In this lesson you learned: chunksize in read_csv returns an iterator yielding one DataFrame per chunk, allowing files larger than RAM to be processed incrementally, partial aggregations (sum + count) accumulate correctly across chunks while mean cannot be averaged directly, and Parquet format is the best long-term alternative to CSV for large datasets due to compression, speed, and dtype preservation. This completes the Pandas Performance Tips course — you are ready for the advanced B2 courses!

Frequently asked questions

Is the “Chunked Reading for Large Files” lesson free?

Yes — the full text of “Chunked Reading for Large Files” is free to read here on the web, and the Pandas & NumPy Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Pandas & NumPy Academy course, upgrade to CoddyKit PRO.

What will I learn in “Chunked Reading for Large Files”?

Process files that exceed RAM by reading in chunks with chunksize in read_csv and aggregating results incrementally. You practise Pandas & NumPy Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Pandas & NumPy Academy?

No prior experience is required. Pandas & NumPy Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Chunked Reading for Large Files” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Pandas & NumPy Academy lesson?

Yes. Every Pandas & NumPy Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Profiling with timeit and memory_profiler
  2. Avoiding iterrows and Python Loops
  3. Efficient Data Types for Memory Reduction
  4. Chunked Reading for Large Files
← Back to Pandas & NumPy Academy