Vai al contenuto principale

pandas .apply(): cos’è, quando è utile e le alternative più veloci

Scopri cos’è .apply di Python pandas e come usarlo con i DataFrame. Impara a iterare sui DataFrame usando oggi stesso la funzione .apply()!
Aggiornato 29 set 2026  · 5 min leggi

Scopri con l'IA

ChatGPTClaudePerplexity

Molti ricorrono a .apply() per “evitare i loop” e si aspettano che sia veloce e lineare. In pratica, l’.apply(axis=1) riga per riga è comunque un loop a livello Python. Può essere lento su dati grandi e a volte restituisce forme che non ti aspettavi. La soluzione è semplice: usa operazioni vettorializzate di pandas/NumPy per i casi comuni e riserva .apply() alla logica che ha davvero bisogno di più colonne.

Questa guida mostra come funziona oggi .apply(), evidenzia le insidie più comuni e fornisce schemi pronti all’uso più veloci e chiari.

Che cos’è DataFrame.apply() e Series.apply()?

DataFrame.apply(func, axis=0) chiama func su ogni colonna per impostazione predefinita. Con axis=1, chiama func su ogni riga. Series.apply(func) chiama func su ogni elemento (o sull’intera Series, a seconda delle firme nelle versioni recenti).

  • Riga per riga vs. colonna per colonna: axis=1 significa “per riga”. axis=0 (predefinito) significa “per colonna”.
  • Regole sulla forma di ritorno (pandas ≥0.23): Se la tua funzione restituisce uno scalare per riga/colonna, ottieni una Series. Se restituisce una Series o un dict, pandas espande quelle chiavi in un DataFrame. Se restituisce una lista/array, otterrai una Series di liste a meno che tu non imposti result_type='expand'.
  • Errori più rigorosi (pandas ≥2.0): Le lunghezze non corrispondenti per oggetti simili a liste ora generano errori invece di produrre silenziosamente risultati incoerenti.

Prima scelta consigliata: operazioni vettorializzate

La maggior parte dei compiti che combinano o trasformano colonne sono più veloci e chiari con espressioni vettorializzate o metodi integrati. L’esempio seguente calcola il differenziale di punti di una squadra di baseball senza .apply().

import pandas as pd

team_stats = pd.DataFrame({
    "Team": ["ARI", "ATL", "BAL"],
    "League": ["NL", "NL", "AL"],
    "Year": [2012, 2012, 2012],
    "RunsScored": [734, 700, 712],
    "RunsAllowed": [688, 600, 705],
    "Wins": [81, 94, 93],
    "Games": [162, 162, 162],
    "Playoffs": [0, 1, 1],
})

# Vectorized: fast and idiomatic
team_stats["RunDiff"] = team_stats["RunsScored"] - team_stats["RunsAllowed"]
print(team_stats[["Team", "RunsScored", "RunsAllowed", "RunDiff"]])

.apply() riga per riga quando ti serve davvero

Usa .apply(axis=1) quando la tua logica coinvolge davvero più colonne e non è facilmente vettorializzabile (per esempio, regole condizionali che dipendono da diversi campi).

calcolare un valore derivato da più colonne

Questo schema calcola un valore per riga usando più input. L’approccio vettorializzato visto sopra è comunque preferibile, ma qui vedi l’uso corretto riga per riga e le opzioni che incidono su velocità e output.

def compute_run_diff(row):
    # Treat the row as read-only; return a scalar
    return row["RunsScored"] - row["RunsAllowed"]

# Row-wise apply (Python-level loop; can be slow on large data)
team_stats["RunDiff_apply"] = team_stats.apply(compute_run_diff, axis=1)

Quando la funzione è numerica e ha bisogno solo di array grezzi, passando raw=True salti parte dell’overhead di pandas fornendo un array NumPy alla tua funzione.

import numpy as np

def compute_run_diff_raw(values):
    # values is a NumPy array when raw=True
    # Order matches the column order we select
    rs, ra = values
    return rs - ra

team_stats["RunDiff_raw"] = team_stats[["RunsScored", "RunsAllowed"]].apply(
    compute_run_diff_raw, axis=1, raw=True
)

verificare una volta l’input della funzione

Quando collego una nuova funzione per riga, stampo una riga una sola volta per confermare cosa viene passato.

def debug_row(row):
    # Print the first row only
    if row.name == team_stats.index[0]:
        print("Example row:", row.to_dict())
    return 0

_ = team_stats.apply(debug_row, axis=1)

Caso d’uso: somme, totali di stagione e flag testuali

Dato un dataset dei Rays per anno, preferisci metodi integrati e strumenti elemento per elemento rispetto a .apply() quando possibile.

rays_by_year = pd.DataFrame(
    {
        "Year": [2012, 2011, 2010, 2009, 2008],
        "RunsScored": [697, 707, 802, 803, 774],
        "RunsAllowed": [577, 614, 649, 754, 671],
        "Wins": [90, 91, 96, 84, 97],
        "Playoffs": [0, 1, 1, 0, 1],
    }
).set_index("Year")

ottenere le somme di colonna in modo efficiente

Usa DataFrame.sum() invece di .apply(sum). È più veloce e più chiaro.

# Preferred
totals = rays_by_year.sum(axis=0)
print(totals)

# If you must use apply (not recommended here)
totals_apply = rays_by_year.apply(sum, axis=0)

calcolare il totale dei punti segnati in una stagione

Usa aritmetica vettoriale tra colonne; .apply() non serve.

rays_by_year["TotalRuns"] = rays_by_year["RunsScored"] + rays_by_year["RunsAllowed"]
print(rays_by_year[["RunsScored", "RunsAllowed", "TotalRuns"]].head())

convertire un flag 0/1 in testo

Preferisci Series.map() o replace() per trasformazioni elemento per elemento su una singola colonna.

# Using map with a dict
rays_by_year["PlayoffsText"] = rays_by_year["Playoffs"].map({0: "No", 1: "Yes"})

# Equivalent with replace
rays_by_year["PlayoffsText2"] = rays_by_year["Playoffs"].replace({0: "No", 1: "Yes"})

Controllare la forma dell’output di .apply()

Quando la tua funzione restituisce più di un valore per riga, rendi esplicita la forma. Eviti sorprese e rotture con versioni future.

restituire una lista ed espanderla in colonne

Usa result_type='expand' per suddividere una lista/array in più colonne.

def wins_losses(row):
    # Derive wins and losses as two outputs
    wins = row["Wins"]
    losses = row["Games"] - row["Wins"] if "Games" in row else np.nan
    return [wins, losses]

# Example using team_stats, which has "Wins" and "Games"
expanded = team_stats.apply(wins_losses, axis=1, result_type="expand")
expanded.columns = ["WinsOut", "LossesOut"]
team_stats = pd.concat([team_stats, expanded], axis=1)

restituire una series o un dict per assegnare i nomi alle colonne automaticamente

Restituire una Series o un dict da ogni riga produce un DataFrame con etichette di colonna corrispondenti.

def summary_row(row):
    return pd.Series(
        {
            "IsWinningSeason": row["Wins"] >= 90,
            "RunRatio": (row["RunsScored"] / row["RunsAllowed"]),
        }
    )

summary = team_stats.apply(summary_row, axis=1)
team_stats = pd.concat([team_stats, summary], axis=1)

Consigli sulle prestazioni che contano

L’.apply(axis=1) riga per riga è comodo ma lento su DataFrame grandi perché chiama la tua funzione Python una volta per riga. Questi schemi evitano quel collo di bottiglia.

  • Preferisci operazioni vettorializzate di pandas/NumPy e metodi integrati come sum, mean, clip, where, astype e i metodi per stringhe/accessor.
  • Per operazioni numeriche su righe/colonne che accettano array, passa raw=True per ottenere un array NumPy e ridurre l’overhead di pandas.
  • Per lavoro elemento per elemento su una singola colonna, usa Series.map() o metodi vettorializzati invece di DataFrame.apply(axis=1).
  • Evita di mutare la riga/colonna fornita all’interno della tua funzione; restituisci invece un nuovo valore.

Note di versione da conoscere (pandas 2.x)

Le versioni recenti di pandas hanno reso più rigoroso il comportamento di .apply() così che i risultati siano più prevedibili.

  • Ritorni tipo lista: dalla 2.0 in poi, lunghezze di lista non coerenti generano un errore. Usa lunghezze consistenti e imposta result_type='expand' quando vuoi più colonne.
  • Espansione dell’output: restituire una Series o un dict si espande in un DataFrame con quelle chiavi come colonne (stabile dalla 0.23).
  • Series.apply() modifiche: l’argomento convert_dtype è deprecato. Se ti servono tipi misti, effettua il cast a object prima (per esempio, s.astype("object").apply(fn)). Le firme più recenti permettono di controllare se fn riceve scalari o una Series in più contesti (per esempio, by_row).

.apply() vs. .map() vs. .applymap() vs. .agg()

Scegli l’API che corrisponde alla forma della tua trasformazione.

  • Series.map(func_or_dict): trasformazione elemento per elemento su una colonna. Ottimo per lookup o funzioni semplici.
  • DataFrame.apply(func, axis=1): logica riga per riga che necessita di più colonne.
  • DataFrame.apply(func, axis=0): logica per colonna (ogni input è una Series che rappresenta una colonna).
  • DataFrame.applymap(func): elemento per elemento su ogni cella. Usalo con parsimonia; i metodi vettorializzati sono più veloci.
  • .agg()/.transform(): aggregazioni e trasformazioni per gruppo; preferiscile nelle pipeline con groupby.

Errori comuni e correzioni rapide

Questi sono i problemi che più spesso causano risultati errati o rallentamenti, con i modi per correggerli.

  • Dimenticare axis=1 per logica per riga: se la tua funzione riceve all’improvviso colonne invece di righe, aggiungi axis=1.
  • Series di liste inattesa: quando restituisci una lista/array da ogni riga, imposta result_type='expand' per suddividerla in colonne.
  • Codice riga per riga lento: sostituiscilo con espressioni vettorializzate o metodi integrati; se non è possibile, valuta raw=True per ridurre l’overhead.
  • Mutare la riga in input: tratta gli input come di sola lettura e restituisci nuovi valori.
  • Deriva di dtype: se la tua funzione a volte restituisce non interi, le colonne intere possono essere promosse a float o object. Esegui il cast dopo con astype se necessario.

Conclusione

.apply() è uno strumento flessibile, ma non è una scorciatoia per le prestazioni. Usa operazioni vettorializzate per aritmetica, aggregazioni e trasformazioni elemento per elemento su una singola colonna. Ricorri a DataFrame.apply(axis=1) solo quando la tua logica ha davvero bisogno di più colonne per riga. Quando la usi, controlla la forma dell’output con result_type, valuta raw=True per funzioni numeriche e tieni d’occhio i dtypes. Questi schemi producono risultati prevedibili con le versioni moderne di pandas e scalano meglio all’aumentare dei dati.

Argomenti
Python

Approfondisci Python e pandas

Corso

Manipolazione dei dati con pandas

4 ore
568.2K
Impara a importare e pulire dati, calcolare statistiche e creare visualizzazioni con pandas.
Vedi i dettagliRight Arrow
Inizia Il Corso
Mostra altroRight Arrow
Correlato

blog

Tokenizzazione nel NLP: come funziona, sfide e casi d'uso

Guida al preprocessing NLP nel machine learning. Copriamo spaCy, i transformer di Hugging Face e come funziona la tokenizzazione in casi d'uso reali.
Abid Ali Awan's photo

Abid Ali Awan

10 min

blog

Che cos'è Snowflake? Guida per principianti alla piattaforma dati cloud

Esplora le basi di Snowflake, la piattaforma dati cloud. Scopri la sua architettura, le sue funzionalità e come integrarla nelle tue pipeline di dati.
Tim Lu's photo

Tim Lu

12 min

blog

I 15 migliori server MCP remoti che ogni AI builder dovrebbe conoscere nel 2026

Scopri i 15 migliori server MCP remoti che stanno trasformando lo sviluppo AI nel 2026. Scopri come migliorano automazione, ragionamento, sicurezza e velocità dei workflow.
Abid Ali Awan's photo

Abid Ali Awan

15 min

Mostra AltroMostra Altro