0Pricing
Pandas & NumPy Academy · Lezione

Tipi di dati efficienti per ridurre la memoria

Riduca il tipo delle colonne numeriche a int32/float32 e converta le colonne stringa in Categorical per ridurre fino al 70% la memoria del DataFrame.

Tipi di dati efficienti per ridurre la memoria è una lezione Pandas & NumPy Academy gratuita su CoddyKit. Questa è la lezione 3 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.

Perché i tipi di dati influenzano le prestazioni

In Pandas, ogni colonna ha un dtype (tipo di dati) che determina come vengono memorizzati i valori e la velocità delle operazioni. Per impostazione predefinita, Pandas utilizza tipi con ampia capacità quando carica i dati: int64 (8 byte per valore), float64 (8 byte) e object (dimensione variabile, spesso 50-200 byte per stringa). Per milioni di righe, scegliere tipi più piccoli può ridurre la memoria del 50-80% e accelerare le operazioni di 2-5 volte grazie a un migliore utilizzo della cache.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'id': np.arange(1000000, dtype='int64'),
    'score': np.random.uniform(0, 100, 1000000).astype('float64'),
    'category': np.random.choice(['A','B','C','D'], 1000000)
})

mem = df.memory_usage(deep=True)
print('Memory per column:')
print(mem)
print(f'Total: {mem.sum() / 1e6:.1f} MB')

Ridurre il tipo delle colonne intere

Se una colonna contiene valori interi che rientrano in un intervallo più piccolo, ne riduca il tipo da int64 a un tipo intero più piccolo. pd.to_numeric(series, downcast='integer') seleziona automaticamente il tipo intero più piccolo in grado di contenere tutti i valori: int8 (da –128 a 127, 1 byte), int16 (da –32768 a 32767, 2 byte), int32 (±2 miliardi, 4 byte), oppure mantiene int64 se necessario. Una colonna int8 utilizza 8 volte meno memoria di una colonna int64.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'age': np.random.randint(18, 90, 500000).astype('int64'),
    'score': np.random.randint(0, 100, 500000).astype('int64'),
    'large_id': np.random.randint(0, 2**31, 500000).astype('int64')
})

# Downcast integers
for col in df.select_dtypes('int64').columns:
    df[col] = pd.to_numeric(df[col], downcast='integer')

print('Dtypes after downcasting:')
print(df.dtypes)
print(f'\nMemory: {df.memory_usage(deep=True).sum()/1e6:.2f} MB')

Ridurre il tipo delle colonne in virgola mobile

pd.to_numeric(series, downcast='float') converte float64 in float32 (4 byte invece di 8) quando la precisione lo consente. float32 ha circa 7 cifre decimali di precisione rispetto alle 15 di float64. Per la maggior parte delle attività di analisi (percentuali, prezzi, caratteristiche normalizzate), la precisione di float32 è sufficiente. Per il calcolo scientifico che richiede un'elevata precisione, mantenga float64. Dimezzare la precisione dei numeri in virgola mobile dimezza la memoria e migliora le prestazioni della cache.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({'lat': np.random.uniform(-90, 90, 1000000),
                   'lon': np.random.uniform(-180, 180, 1000000),
                   'temp': np.random.uniform(-40, 50, 1000000)})

print('Before:', df.dtypes.unique())
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')

for col in df.select_dtypes('float64').columns:
    df[col] = pd.to_numeric(df[col], downcast='float')

print('After:', df.dtypes.unique())
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')

Il tipo di dati Categorical

Il dtype Categorical offre il maggiore risparmio di memoria per le colonne di stringhe con valori ripetuti. Invece di memorizzare migliaia di volte la stessa stringa (ad esempio 'Electronics'), Pandas memorizza un dizionario dei valori univoci e un codice intero compatto per ogni riga. Una colonna con 1 milione di righe e solo 50 categorie univoche passa da circa 50 MB (dtype object) a circa 1 MB (Categorical): una riduzione della memoria di 50 volte.

import pandas as pd
import numpy as np

np.random.seed(0)
categories = ['Electronics', 'Clothing', 'Books', 'Food', 'Furniture',
              'Sports', 'Toys', 'Health', 'Garden', 'Automotive']

df = pd.DataFrame({'product_cat': np.random.choice(categories, 1000000)})

mem_before = df.memory_usage(deep=True).sum()
df['product_cat'] = df['product_cat'].astype('category')
mem_after = df.memory_usage(deep=True).sum()

print(f'Object dtype: {mem_before/1e6:.1f} MB')
print(f'Categorical:  {mem_after/1e6:.2f} MB')
print(f'Reduction: {mem_before/mem_after:.0f}x')

Quando Categorical riduce la memoria

Il dtype Categorical riduce la memoria solo quando la colonna ha molti meno valori univoci rispetto al numero totale di righe. Il punto di pareggio si raggiunge quando il rapporto tra valori univoci e righe totali (cardinalità) è superiore a circa il 50%: se ogni riga contiene una stringa univoca, Categorical utilizza in realtà più memoria del dtype object, perché memorizza sia l'array dei codici sia l'array delle categorie. Controlli sempre df['col'].nunique() / len(df) prima della conversione: un rapporto inferiore a 0,5 (e idealmente inferiore a 0,05) indica che Categorical sarà utile.

import pandas as pd
import numpy as np

def memory_gain(df, col):
    n = len(df)
    n_unique = df[col].nunique()
    ratio = n_unique / n
    mem_obj = df[col].memory_usage(deep=True)
    df2 = df.copy()
    df2[col] = df2[col].astype('category')
    mem_cat = df2[col].memory_usage(deep=True)
    print(f'{col}: {n_unique} unique / {n} total (ratio={ratio:.3f})')
    print(f'  Object: {mem_obj/1e6:.2f} MB → Categorical: {mem_cat/1e6:.2f} MB')
    print(f'  {"Saves" if mem_cat < mem_obj else "Wastes"} {abs(mem_obj-mem_cat)/1e6:.2f} MB')

np.random.seed(0)
df = pd.DataFrame({
    'low_card': np.random.choice(['A','B','C'], 500000),   # low cardinality
    'high_card': [f'id_{i}' for i in range(500000)]         # high cardinality
})
memory_gain(df, 'low_card')
memory_gain(df, 'high_card')

Categorical migliora le prestazioni di GroupBy

Oltre a ridurre la memoria, il dtype Categorical accelera le operazioni groupby, perché Pandas può utilizzare direttamente i codici interi invece di calcolare l'hash delle stringhe per individuare i limiti dei gruppi. Per i DataFrame con milioni di righe raggruppate in base a colonne stringa con bassa cardinalità (come regione, categoria di prodotto o stato), convertire tali colonne in Categorical prima di groupby può produrre miglioramenti di velocità pari a 2-5 volte.

import pandas as pd
import numpy as np
import timeit

np.random.seed(0)
df = pd.DataFrame({
    'region': np.random.choice(['North','South','East','West'], 1000000),
    'sales': np.random.randn(1000000)
})

# Object dtype groupby
t1 = timeit.timeit(lambda: df.groupby('region')['sales'].mean(), number=50)

# Categorical dtype groupby
df2 = df.copy()
df2['region'] = df2['region'].astype('category')
t2 = timeit.timeit(lambda: df2.groupby('region')['sales'].mean(), number=50)

print(f'Object dtype groupby: {t1/50*1000:.2f} ms')
print(f'Categorical groupby:  {t2/50*1000:.2f} ms')
print(f'Speedup: {t1/t2:.1f}x')

Utilizzare il dtype booleano per le colonne flag

Le colonne binarie (True/False, 0/1, yes/no) sono spesso memorizzate come object o int64. Convertirle al dtype bool utilizza solo 1 byte per valore (rispetto agli 8 byte di int64 o agli oltre 50 byte delle stringhe 'yes'/'no'). Utilizzi astype(bool) dopo essersi assicurato che la colonna contenga esclusivamente valori 0/1 o True/False. Le colonne booleane consentono inoltre filtri più veloci, perché Pandas può utilizzare internamente operazioni bit a bit.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'is_premium': np.random.choice([0, 1], 1000000).astype('int64'),
    'has_discount': np.random.choice(['yes', 'no'], 1000000)
})

print('Before:')
print(df.dtypes)
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.1f} MB')

df['is_premium'] = df['is_premium'].astype(bool)
df['has_discount'] = df['has_discount'].map({'yes': True, 'no': False}).astype(bool)

print('\nAfter:')
print(df.dtypes)
print(f'Memory: {df.memory_usage(deep=True).sum()/1e6:.2f} MB')

Automatizzare l'ottimizzazione dei tipi

Scriva una funzione riutilizzabile optimise_dtypes(df) che applichi automaticamente tutte le ottimizzazioni: riduzione del tipo degli interi, riduzione del tipo dei numeri decimali, conversione degli oggetti con poche modalità in Categorical e conversione degli interi 0/1 in bool. Esegua questa funzione al caricamento dei dati per ridurre al minimo l'utilizzo della memoria fin dall'inizio. In questo modo, ogni membro del team che carica lo stesso dataset ottiene la versione ottimizzata senza dover ricordare di applicare manualmente ogni passaggio.

import pandas as pd
import numpy as np

def optimise_dtypes(df, cat_threshold=0.5):
    '''Reduce DataFrame memory by choosing smaller dtypes.'''
    for col in df.columns:
        col_type = df[col].dtype
        if col_type == 'int64':
            df[col] = pd.to_numeric(df[col], downcast='integer')
        elif col_type == 'float64':
            df[col] = pd.to_numeric(df[col], downcast='float')
        elif col_type == 'object':
            cardinality = df[col].nunique() / len(df)
            if cardinality < cat_threshold:
                df[col] = df[col].astype('category')
    return df

# Test
np.random.seed(0)
df = pd.DataFrame({'a': np.random.randint(0,100,500000),
                   'b': np.random.randn(500000),
                   'c': np.random.choice(['X','Y','Z'],500000)})

before = df.memory_usage(deep=True).sum()
df = optimise_dtypes(df)
after = df.memory_usage(deep=True).sum()
print(f'Before: {before/1e6:.2f} MB → After: {after/1e6:.2f} MB ({before/after:.1f}x reduction)')

Tipi interi senza segno per dati non negativi

Quando una colonna contiene solo interi non negativi (come ID, conteggi o quantità), utilizzi i tipi interi senza segno: uint8 (0–255), uint16 (0–65535), uint32 (0–4 miliardi) o uint64. I tipi senza segno hanno la stessa dimensione in byte dei corrispondenti tipi con segno, ma possono memorizzare valori positivi fino al doppio. Ad esempio, un ID prodotto compreso tra 0 e 60.000 può essere memorizzato in uint16 (2 byte) anziché in int32 (4 byte). Utilizzi astype('uint16') dopo aver verificato che la colonna non contenga valori negativi.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'product_id': np.random.randint(0, 50000, 1000000).astype('int64'),
    'quantity': np.random.randint(0, 255, 1000000).astype('int64')
})

print('Before (int64):', df.memory_usage(deep=True).sum() / 1e6, 'MB')

# product_id fits in uint16 (0-65535)
df['product_id'] = df['product_id'].astype('uint16')
# quantity fits in uint8 (0-255)
df['quantity'] = df['quantity'].astype('uint8')

print('After (uint16+uint8):', df.memory_usage(deep=True).sum() / 1e6, 'MB')
print('\nDtypes:', df.dtypes.to_dict())

Controllare la memoria dopo ogni passaggio di ottimizzazione

Applichi le ottimizzazioni una alla volta e controlli la memoria dopo ogni passaggio. In questo modo può vedere esattamente quanto contribuisce ciascuna tecnica. Un DataFrame di grandi dimensioni può passare tipicamente da 2 GB (tutti i dtype predefiniti) a 600 MB (riduzione del tipo degli interi), poi a 300 MB (riduzione del tipo dei numeri decimali) e infine a 200 MB (Categorical per le colonne stringa). Documentare questa suddivisione aiuta a giustificare la maggiore complessità ai membri del team che vedono dtype non familiari nel codice.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'user_id': np.random.randint(0, 99999, 1000000).astype('int64'),
    'age': np.random.randint(18, 80, 1000000).astype('int64'),
    'revenue': np.random.uniform(0, 5000, 1000000).astype('float64'),
    'country': np.random.choice(['US','UK','DE','FR','JP'], 1000000),
    'tier': np.random.choice(['free','basic','pro','enterprise'], 1000000)
})

def mem_mb(df):
    return df.memory_usage(deep=True).sum() / 1e6

print(f'Start: {mem_mb(df):.1f} MB')

for c in ['user_id','age']:
    df[c] = pd.to_numeric(df[c], downcast='integer')
print(f'After int downcast: {mem_mb(df):.1f} MB')

df['revenue'] = pd.to_numeric(df['revenue'], downcast='float')
print(f'After float downcast: {mem_mb(df):.1f} MB')

for c in ['country','tier']:
    df[c] = df[c].astype('category')
print(f'After Categorical: {mem_mb(df):.1f} MB')

Salvare e ricaricare i tipi ottimizzati

I dtype ottimizzati vengono persi se si salva in formato CSV (che converte nuovamente tutto in testo). Per conservare i dtype, salvi in formato Parquet con df.to_parquet('file.parquet'): Parquet conserva i tipi int32, float32 e Categorical. Quando il file viene ricaricato con pd.read_parquet, il DataFrame mantiene gli stessi tipi efficienti in termini di memoria senza dover eseguire nuovamente la funzione di ottimizzazione. Per i file di grandi dimensioni, Parquet viene inoltre caricato molto più velocemente del CSV.

import pandas as pd
import numpy as np

np.random.seed(0)
df = pd.DataFrame({
    'age': np.random.randint(18, 80, 100000).astype('int8'),   # already optimised
    'score': np.random.randn(100000).astype('float32'),
    'tier': pd.Categorical(np.random.choice(['free','pro'], 100000))
})
print('Dtypes:', df.dtypes.to_dict())

# Save to Parquet (preserves dtypes)
df.to_parquet('/tmp/optimised.parquet', index=False)

# Reload — dtypes preserved!
df_loaded = pd.read_parquet('/tmp/optimised.parquet')
print('\nLoaded dtypes:', df_loaded.dtypes.to_dict())
print(f'Memory: {df_loaded.memory_usage(deep=True).sum()/1e6:.2f} MB')

Controllo rapido

Verifichi la Sua comprensione dei tipi di dati efficienti in termini di memoria presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: pd.to_numeric(downcast='integer') riduce le colonne intere da 8 byte a 1–4 byte, pd.to_numeric(downcast='float') dimezza la memoria occupata dai numeri decimali, il dtype Categorical riduce fino a 50 volte le colonne stringa con poche modalità e accelera anche il groupby, mentre il formato Parquet conserva i dtype ottimizzati durante i cicli di salvataggio e caricamento dei file. Ora analizzeremo la lettura a blocchi, che consente di elaborare file più grandi della RAM disponibile.

Domande Frequenti

La lezione «Tipi di dati efficienti per ridurre la memoria» è gratuita?

Sì — il testo completo di «Tipi di dati efficienti per ridurre la memoria» è 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 «Tipi di dati efficienti per ridurre la memoria»?

Riduca il tipo delle colonne numeriche a int32/float32 e converta le colonne stringa in Categorical per ridurre fino al 70% la memoria del DataFrame. 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 3 di 4.

Quanto tempo richiede la lezione «Tipi di dati efficienti per ridurre la memoria»?

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