apply n’est pas une primitive compilée. C’est une boucle Python habillée en méthode : pour chaque ligne ou chaque colonne, l’interpréteur appelle votre fonction, construit un objet, le range. Avec axis=1, c’est pire : chaque ligne devient une Series, avec son constructeur et son index.
df.apply(lambda r: r["ht"] * (1 + r["tva"]), axis=1) # lent
df["ht"] * (1 + df["tva"]) # vectoriséMême intention. Deux ordres de grandeur sur quelques centaines de milliers de lignes, souvent plus.
Series.apply(str.lower) est le cousin fréquent : vous avez un accesseur vectorisé, .str.lower(), et vous l’ignorez.
Dans cet ordre, celui d’un entretien qui avance :
clip, fillna, astype..str, .dt, .cat. Ils sont faits pour ça.np.where, np.select, np.log1p, ufuncs sur .to_numpy().map sur une table — dictionnaire de recodage, pas une fonction Python par cellule si un vecteur suffit.groupby.transform / agg — dès que le calcul est intra-groupe.df["segment"] = np.select(
[df["montant"] >= 1000, df["montant"] >= 100],
["haut", "moyen"],
default="bas",
)Trois branches, zéro apply.
apply reste défendableUne logique vraiment irrégulière : parser un JSON mal formé différent à chaque ligne, appeler une API, une règle métier de trente exceptions qui ne se factorise pas. Encore là, vous testez d’abord un .str.extract ou un json_normalize. Et vous le faites sur un échantillon : si 50 ms × n lignes dépasse votre budget, ce n’est plus Pandas, c’est un job.
groupby.apply pour un modèle par groupe ou une fenêtre custom existe ; ce n’est pas un permis de l’utiliser pour une somme.
Pas que vous haïssez apply. Que vous savez ce qu’il coûte et que votre premier réflexe est la forme vectorisée. Un candidat qui écrit axis=1 pour additionner deux colonnes n’a pas encore travaillé un fichier de production.