Pandas & NumPy Academy · Lezione

Lettura a blocchi di file di grandi dimensioni

Elabori i file che superano la RAM leggendoli a blocchi con chunksize in read_csv e aggregando progressivamente i risultati.

Lezione 4 di 413 passaggi

Lettura a blocchi di file di grandi dimensioni è una lezione Pandas & NumPy Academy gratuita su CoddyKit. Questa è la lezione 4 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Pandas & NumPy Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Pandas & NumPy Academy include 4 lezioni in totale.

Quando i file superano la RAM

Un collo di bottiglia comune nelle pipeline di dati in produzione è un file CSV più grande della RAM disponibile. Se un file è di 20 GB e il computer dispone di 16 GB di RAM, pd.read_csv('file.csv') si interromperà con un MemoryError. La soluzione è la lettura a blocchi: caricare il file in parti di dimensione fissa, elaborare ogni parte e accumulare i risultati. In questo modo può analizzare file di dimensioni arbitrarie senza caricarli interamente in memoria.

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

Il parametro chunksize in read_csv

Passi chunksize=n a pd.read_csv() per ottenere un iteratore TextFileReader anziché un DataFrame. Ogni iterazione restituisce le n righe successive come DataFrame. In questo modo, in memoria sono presenti solo n righe alla volta. Un buon valore iniziale per chunksize è 100.000 righe: abbastanza piccolo da entrare nella RAM, ma abbastanza grande da mantenere gestibile l'overhead di I/O. Lo adatti in base alla RAM disponibile e al numero di colonne.

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

Iterare sui blocchi

Utilizzi un ciclo for sull'iteratore dei blocchi per elaborare ciascun blocco. Per operazioni semplici, come contare le righe, sommare una colonna o filtrare, elabori ogni blocco indipendentemente e accumuli i risultati in un elenco o in un totale progressivo. Chiami sempre l'iteratore all'interno di un'istruzione with oppure lo ricrei per ogni esecuzione della pipeline: gli iteratori vengono consumati una sola volta e non possono essere riavviati.

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

Filtrare le righe durante l'elaborazione a blocchi

Applichi i filtri sulle righe all'interno del ciclo, così da scartare i dati indesiderati prima che vengano accumulati. In questo modo l'utilizzo della memoria rimane contenuto, perché vengono conservate solo le righe che soddisfano i criteri. Ad esempio, per estrarre tutte le righe della regione 'North' da un file di 10 GB, filtri ogni blocco prima di aggiungerlo all'elenco dei risultati. Il pd.concat finale gestisce solo il sottoinsieme filtrato, molto più piccolo.

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

Aggregazione incrementale con GroupBy

Le aggregazioni GroupBy su più blocchi sono più complesse delle semplici somme, perché i gruppi possono estendersi su più blocchi. Il procedimento è il seguente: calcolare somme e conteggi a livello di gruppo per ogni blocco, concatenare questi risultati parziali ed eseguire un groupby finale sui risultati parziali accumulati. Non chiami mai groupby().mean() per ogni blocco per poi calcolare la media delle medie: si otterrebbero risultati errati quando le dimensioni dei gruppi differiscono tra i blocchi.

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

Specificare tipi efficienti in termini di memoria al caricamento

Riduca l'utilizzo della memoria durante il caricamento a blocchi specificando in anticipo i dtypes delle colonne in read_csv. In questo modo impedisce a Pandas di allocare tipi troppo grandi per ogni blocco e di scartarli subito dopo. Passi a read_csv() un dizionario come dtype={'region': 'category', 'sales': 'int32'}. Insieme alla lettura a blocchi, questa tecnica può ridurre la memoria di picco a meno del 10% rispetto a un caricamento ingenuo dell'intero file.

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

Scrivere i risultati in modo incrementale

Quando è necessario scrivere in un file anche l'output dell'elaborazione di ogni blocco, scriva ogni blocco elaborato man mano, invece di accumulare tutto in memoria. Utilizzi mode='a' (aggiunta) e header=False (dopo il primo blocco) con to_csv(). In questo modo l'occupazione di memoria sia dell'input sia dell'output rimane pari alla dimensione del blocco e la pipeline può elaborare file di dimensioni arbitrarie anche su un computer con memoria limitata.

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

Introduzione a Dask come alternativa immediata

Dask offre un'API simile a quella di Pandas che esegue le operazioni in modo differito e in parallelo sui blocchi, automaticamente. Anziché scrivere manualmente un ciclo di elaborazione a blocchi, scriva dask_df = dd.read_csv('file.csv') e poi utilizzi le stesse operazioni di Pandas: groupby, filtro e merge. Chiami .compute() alla fine per avviare l'esecuzione. Dask è la soluzione più pratica quando la pipeline comprende operazioni complesse in più passaggi, difficili da implementare manualmente con l'elaborazione a blocchi.

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

Scegliere la dimensione dei blocchi

La dimensione ideale dei blocchi bilancia due fattori contrapposti: una dimensione troppo piccola (ad esempio 1.000 righe) comporta un overhead elevato per ogni blocco (configurazione dell'I/O del file e creazione del DataFrame) e rende più lungo il ciclo. Una dimensione troppo grande (ad esempio 10 milioni di righe) vanifica lo scopo, caricando troppi dati in RAM contemporaneamente. Un punto di partenza pratico è scegliere blocchi che occupino in memoria 50–200 MB. Per un DataFrame con 10 colonne che occupano in media 8 byte ciascuna, 100.000 righe corrispondono a circa 8 MB per blocco: un valore predefinito sicuro, che lascia molta memoria disponibile.

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 e CSV a confronto per i dati di grandi dimensioni

Se può controllare il formato del file, preferisca Parquet al CSV per i dataset di grandi dimensioni. Parquet è un formato binario colonnare che carica solo le colonne richieste (column pruning), comprime molto meglio del CSV, conserva i dtype (senza nuova inferenza al caricamento) e viene letto da 5 a 20 volte più velocemente di un CSV equivalente. Converta una volta un CSV di grandi dimensioni in Parquet con pd.read_csv('file.csv', chunksize=500000) + to_parquet, quindi utilizzi pd.read_parquet(columns=['col1','col2']) per tutte le letture successive.

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

Schema completo di una pipeline a blocchi

Uno schema completo per elaborare file di grandi dimensioni: 1) specifichi i dtype al momento della lettura; 2) filtri le righe il prima possibile all'interno del ciclo; 3) calcoli aggregazioni parziali per ogni blocco (somma + conteggio, mai la media direttamente); 4) al termine del ciclo, combini i risultati parziali e calcoli le statistiche finali; 5) scriva l'output in modo incrementale se i risultati sono voluminosi. Questo schema gestisce file di qualsiasi dimensione su qualsiasi computer, regolando adeguatamente chunksize.

Controllo rapido

Verifichi la Sua comprensione della lettura a blocchi presentata in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che chunksize in read_csv restituisce un iteratore che produce un DataFrame per ogni blocco, consentendo di elaborare in modo incrementale file più grandi della RAM; le aggregazioni parziali (somma + conteggio) si accumulano correttamente tra i blocchi, mentre la media non può essere calcolata direttamente come media delle medie; infine, il formato Parquet è la migliore alternativa a lungo termine al CSV per i dataset di grandi dimensioni, grazie a compressione, velocità e conservazione dei dtype. Con questo si conclude il corso sui suggerimenti per le prestazioni di Pandas: ora è pronto per i corsi avanzati B2!

Gratis per iniziare

Impara Python con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
30
Lezioni
120

Domande Frequenti

La lezione «Lettura a blocchi di file di grandi dimensioni» è gratuita?

Sì — il testo completo di «Lettura a blocchi di file di grandi dimensioni» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Pandas & NumPy Academy, passa a CoddyKit PRO. Il corso Pandas & NumPy Academy include 4 lezioni in totale.

Cosa imparerò in «Lettura a blocchi di file di grandi dimensioni»?

Elabori i file che superano la RAM leggendoli a blocchi con chunksize in read_csv e aggregando progressivamente i risultati. Eserciti Pandas & NumPy Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Pandas & NumPy Academy?

Non è richiesta alcuna esperienza precedente. Pandas & NumPy Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 4 di 4.

Quanto tempo richiede la lezione «Lettura a blocchi di file di grandi dimensioni»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Pandas & NumPy Academy?

Sì. Ogni lezione Pandas & NumPy Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Profiling con timeit e memory_profiler
  2. Evitare iterrows e i cicli Python
  3. Tipi di dati efficienti per ridurre la memoria
  4. Lettura a blocchi di file di grandi dimensioni
← Torna a Pandas & NumPy Academy