0Pricing
Pandas & NumPy Academy · Lezione

Vantaggi prestazionali degli indici ordinati

Ordini un MultiIndex con sort_index(), misuri le prestazioni delle slice con timeit e usi is_monotonic_increasing come controllo di sicurezza.

Vantaggi prestazionali degli indici ordinati è 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.

Perché l’ordinamento degli indici è importante per le prestazioni

Un indice ordinato consente a Pandas di utilizzare la ricerca binaria (O(log n)) invece di una scansione lineare completa (O(n)) per cercare intervalli di etichette. In un DataFrame con un milione di righe, la ricerca binaria trova l’intervallo di destinazione con circa 20 confronti, contro un massimo di un milione di confronti con una scansione lineare. Questo rende le operazioni di slicing su MultiIndex ordinati più veloci di diversi ordini di grandezza rispetto a quelle su indici non ordinati — e la differenza diventa fondamentale nelle pipeline di produzione che elaborano milioni di righe.

import pandas as pd
import numpy as np

np.random.seed(42)
# Create a large DataFrame with a MultiIndex
countries = ['DE', 'UK', 'USA', 'FR', 'JP']
dates = pd.date_range('2020-01-01', periods=200)
mi = pd.MultiIndex.from_product([countries, dates], names=['country', 'date'])
df = pd.DataFrame({'value': np.random.randn(len(mi))}, index=mi)

print(f'DataFrame shape: {df.shape}')
print(f'Index is sorted: {df.index.is_monotonic_increasing}')

Verificare se un indice è ordinato

Utilizzi df.index.is_monotonic_increasing per verificare se l’indice è ordinato in ordine crescente. Il risultato è un booleano. Per un MultiIndex, Pandas verifica l’ordinamento lessicografico su tutti i livelli. Esegua sempre questo controllo prima delle operazioni di slicing con .loc[start:end] su un MultiIndex: un indice non ordinato genera UnsortedIndexError oppure restituisce silenziosamente risultati errati, a seconda della versione di Pandas.

import pandas as pd

# Sorted MultiIndex
tuples_sorted = [('A', 1), ('A', 2), ('B', 1), ('B', 2)]
mi_sorted = pd.MultiIndex.from_tuples(tuples_sorted)
df_sorted = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi_sorted)

# Unsorted MultiIndex
tuples_unsorted = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi_unsorted = pd.MultiIndex.from_tuples(tuples_unsorted)
df_unsorted = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi_unsorted)

print('Sorted index is_monotonic_increasing:', df_sorted.index.is_monotonic_increasing)
print('Unsorted index is_monotonic_increasing:', df_unsorted.index.is_monotonic_increasing)

Ordinamento con sort_index()

df.sort_index() restituisce un nuovo DataFrame con le righe ordinate per etichetta dell’indice in ordine crescente. Utilizzi ascending=False per l’ordine decrescente. Per un MultiIndex, l’ordinamento è lessicografico: prima ordina in base al livello più esterno, poi in base ai livelli interni all’interno di ogni gruppo esterno. Ordini sempre l’indice dopo qualsiasi operazione che potrebbe alterarlo — ad esempio pd.concat, il filtraggio o l’aggiunta di nuove righe.

import pandas as pd
import numpy as np

np.random.seed(0)
countries = ['USA', 'UK', 'DE']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'gdp': np.random.randint(3000, 26000, 9)}, index=mi)

print('Before sort_index():')
print(df.head(4))

df_sorted = df.sort_index()
print('\nAfter sort_index():')
print(df_sorted.head(4))
print('Is sorted:', df_sorted.index.is_monotonic_increasing)

Misurare il tempo di ricerca con timeit

Il modulo Python timeit misura il tempo necessario per eseguire un’istruzione eseguendola molte volte e calcolandone la media. Lo utilizzi per confrontare le ricerche su indici ordinati e non ordinati. In IPython/Jupyter, la magic %timeit offre la stessa funzionalità con un output più leggibile. Il benchmarking è l’unico metodo affidabile per verificare che un’ottimizzazione delle prestazioni abbia effettivamente prodotto un miglioramento: non dia mai per scontato che una modifica sia più veloce senza misurarla.

import pandas as pd
import numpy as np
import timeit

np.random.seed(0)
N = 500000
idx = np.random.choice(['A','B','C','D','E'], N)
df_unsorted = pd.DataFrame({'v': np.random.randn(N)}, index=idx)
df_sorted = df_unsorted.sort_index()

# Time label lookup: sorted vs unsorted
t_unsorted = timeit.timeit(lambda: df_unsorted.loc['C'], number=100)
t_sorted = timeit.timeit(lambda: df_sorted.loc['C'], number=100)

print(f'Unsorted lookup (100 runs): {t_unsorted:.3f}s')
print(f'Sorted lookup  (100 runs): {t_sorted:.3f}s')
print(f'Speedup: {t_unsorted/t_sorted:.1f}x')

PerformanceWarning da un MultiIndex non ordinato

Pandas emette un PerformanceWarning quando si esegue lo slicing di un MultiIndex non ordinato lessicograficamente: 'indexing past lexsort depth may impact performance'. Questo avviso indica che Pandas ha dovuto ricorrere a una scansione lineare invece della ricerca binaria. Sebbene nei casi semplici restituisca comunque risultati corretti, può restituire risultati errati quando si esegue lo slicing dei livelli interni di un indice multilivello non ordinato. Tratti questo avviso come un errore e risolva la causa alla radice ordinando l’indice.

import pandas as pd
import warnings

# Create an unsorted MultiIndex and trigger the warning
tuples = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi = pd.MultiIndex.from_tuples(tuples, names=['letter', 'num'])
df = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi)

print('Index sorted?', df.index.is_monotonic_increasing)

# This may trigger PerformanceWarning in some Pandas versions
with warnings.catch_warnings(record=True) as w:
    warnings.simplefilter('always')
    try:
        result = df.loc['A':'B', :]
        print('Result:', result)
        if w:
            print('Warning:', str(w[0].message))
    except Exception as e:
        print('Error (common with newer Pandas):', type(e).__name__)

Benchmark dello slicing di un MultiIndex ordinato e non ordinato

Lo slicing di un MultiIndex ordinato è notevolmente più veloce perché Pandas può eseguire una ricerca binaria sia sugli array del livello esterno sia su quelli del livello interno. Eseguiamo il benchmark dello slicing di un MultiIndex di grandi dimensioni con 1 milione di righe, una dimensione comune nelle pipeline di analisi di produzione. La versione ordinata evita la scansione lineare e mostra costantemente accelerazioni da 5 a 50 volte, a seconda della selettività dello slice.

import pandas as pd
import numpy as np
import timeit

np.random.seed(42)
countries = ['DE', 'UK', 'USA', 'FR', 'JP']
dates = pd.date_range('2010-01-01', periods=200000)

# Sample a random subset for timing test
sample_countries = np.random.choice(countries, 100000)
sample_dates = np.random.choice(dates, 100000)

df = pd.DataFrame({
    'country': sample_countries,
    'date': sample_dates,
    'value': np.random.randn(100000)
}).set_index(['country', 'date'])

df_sorted = df.sort_index()
print('Dataset size:', len(df))
print('Sorted:', df_sorted.index.is_monotonic_increasing)

t = timeit.timeit(lambda: df_sorted.loc['USA'], number=50)
print(f'Sorted lookup (50 runs): {t:.3f}s')

sort_index con il parametro level

Per un MultiIndex, può ordinare in base a un livello specifico invece che a tutti i livelli utilizzando il parametro level: df.sort_index(level='year'). È utile quando desidera mantenere il raggruppamento del livello esterno, riordinando però le righe all’interno di ciascun gruppo esterno. L’argomento sort_remaining=True (predefinito) ordina anche gli eventuali livelli non ordinati oltre a quello specificato, garantendo un ordinamento lessicografico completo.

import pandas as pd
import numpy as np

countries = ['USA', 'UK']
years = [2023, 2021, 2022]  # deliberately unordered
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'v': range(6)}, index=mi)

print('Before sorting by year level:')
print(df)

# Sort by the inner level (year) only
df_ysorted = df.sort_index(level='year')
print('\nAfter sort_index(level="year"):')
print(df_ysorted)

is_monotonic_increasing come protezione della pipeline

Nelle pipeline di produzione, aggiunga una protezione sull’ordinamento all’inizio di ogni funzione che riceve un DataFrame con un MultiIndex ed esegue operazioni di slicing. Se l’indice non è ordinato, lo ordini automaticamente e registri un avviso. In questo modo si evitano cali silenziosi delle prestazioni o risultati errati quando il codice a monte modifica l’ordine del DataFrame. Una protezione al confine della funzione è più affidabile che presumere che i chiamanti passino sempre dati ordinati.

import pandas as pd
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def safe_slice(df, key):
    '''Slice a MultiIndex DataFrame, sorting if necessary.'''
    if not df.index.is_monotonic_increasing:
        logger.warning('Index not sorted — sorting now. This is a performance cost.')
        df = df.sort_index()
    return df.loc[key]

# Test with an unsorted DataFrame
tuples = [('B', 2), ('A', 1), ('B', 1), ('A', 2)]
mi = pd.MultiIndex.from_tuples(tuples, names=['letter', 'num'])
df = pd.DataFrame({'v': [10, 20, 30, 40]}, index=mi)

result = safe_slice(df, 'A')
print('Slice result for A:')
print(result)

Indice ordinato per la ricerca binaria su indici semplici

I vantaggi in termini di prestazioni dell’ordinamento si applicano anche agli indici regolari (non multi). Un DatetimeIndex utilizzato nell’analisi di serie temporali esegue molto più rapidamente lo slicing di intervalli di date quando è ordinato. Un indice di stringhe ordinato alfabeticamente consente la ricerca binaria delle etichette. Per una Series di grandi dimensioni contenente prezzi azionari indicizzati per timestamp, ordinare il DatetimeIndex può trasformare uno slicing di 100 ms in un’operazione da meno di un millisecondo.

import pandas as pd
import numpy as np
import timeit

np.random.seed(0)
# Random timestamps — unsorted
timestamps = pd.date_range('2020-01-01', periods=500000, freq='min')
shuffled = np.random.permutation(timestamps)

prices = pd.Series(np.random.randn(500000), index=shuffled)
prices_sorted = prices.sort_index()

# Time a date range slice
t_unsorted = timeit.timeit(lambda: prices['2020-06-01':'2020-06-30'], number=20)
t_sorted = timeit.timeit(lambda: prices_sorted['2020-06-01':'2020-06-30'], number=20)

print(f'Unsorted: {t_unsorted:.3f}s')
print(f'Sorted:   {t_sorted:.3f}s')
print(f'Speedup: {t_unsorted/t_sorted:.0f}x')

Costo in memoria dell’ordinamento

L’ordinamento non è gratuito: sort_index() crea una nuova copia del DataFrame (a meno che non si utilizzi inplace=True, che modifica il DataFrame direttamente). Per DataFrame molto grandi, questo raddoppia temporaneamente il picco di utilizzo della memoria. Una strategia pratica consiste nell’ordinare una sola volta al caricamento e mantenere la versione ordinata per tutta la pipeline, invece di ordinare ripetutamente. Se la memoria è limitata, ordini direttamente il DataFrame con df.sort_index(inplace=True) per evitare la copia temporanea.

import pandas as pd
import numpy as np

np.random.seed(0)
countries = ['DE', 'UK', 'USA']
years = [2021, 2022, 2023]
mi = pd.MultiIndex.from_product([countries, years], names=['country', 'year'])
df = pd.DataFrame({'v': np.random.randn(9)}, index=mi)

# Sort once at load time — best practice
df.sort_index(inplace=True)  # no temporary copy
assert df.index.is_monotonic_increasing, 'Index must be sorted!'
print('Pipeline-ready DataFrame (sorted in place):')
print(df)

Riepilogo delle best practice per gli indici ordinati

Regole fondamentali per le prestazioni degli indici: 1) Chiami sempre sort_index() dopo qualsiasi operazione che potrebbe alterare l’ordine dell’indice (concat, merge, append, filtro). 2) Utilizzi is_monotonic_increasing come protezione nelle funzioni che eseguono lo slicing dell’indice. 3) Ordini i dati al caricamento e mantenga il DataFrame ordinato per tutta la pipeline, evitando ordinamenti ripetuti. 4) Per i DataFrame con MultiIndex, si assicuri che siano ordinati tutti i livelli, non solo quello più esterno. 5) Utilizzi timeit per verificare che l’ordinamento offra effettivamente l’accelerazione prevista nella pipeline specifica.

Verifica rapida

Verifichi la sua comprensione delle prestazioni degli indici ordinati presentate in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che is_monotonic_increasing verifica se un indice è ordinato e se è disponibile la ricerca binaria, sort_index() ordina direttamente oppure restituisce una copia ordinata, e timeit misura l’effettiva accelerazione per confermare il vantaggio. Nella prossima lezione esploreremo le funzioni di finestra: statistiche rolling ed expanding per serie temporali e dati finanziari.

Domande Frequenti

La lezione «Vantaggi prestazionali degli indici ordinati» è gratuita?

Sì — il testo completo di «Vantaggi prestazionali degli indici ordinati» è 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 «Vantaggi prestazionali degli indici ordinati»?

Ordini un MultiIndex con sort_index(), misuri le prestazioni delle slice con timeit e usi is_monotonic_increasing come controllo di sicurezza. 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 «Vantaggi prestazionali degli indici ordinati»?

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. Creare un MultiIndex
  2. Selezionare dati da un MultiIndex
  3. Allineamento e reindicizzazione degli indici
  4. Vantaggi prestazionali degli indici ordinati
← Torna a Pandas & NumPy Academy