Rapide, flexible, facile et intuitif: comment accélérer vos projets Pandas

Rapide, flexible, facile et intuitif: comment accélérer vos projets Pandas

Si vous travaillez avec de grands ensembles de données, vous vous souvenez probablement du moment «aha» de votre parcours Python lorsque vous avez découvert la bibliothèquePandas. Pandas change la donne pourdata science and analytics, en particulier si vous êtes arrivé à Python parce que vous recherchiez quelque chose de plus puissant que Excel et VBA.

Alors, qu'en est-il des Pandas qui ont des scientifiques des données, des analystes et des ingénieurs comme moi en délire? Eh bien, la documentation Pandas dit qu'elle utilise:

"fast,flexible et structures de données expressives conçues pour travailler avec des données" relationnelles "ou" étiquetées "à la foiseasy etintuitive."

Rapide, flexible, facile et intuitif? Ça sonne bien! Si votre travail consiste à créer des modèles de données complexes, vous ne voulez pas passer la moitié de vos heures de développement à attendre que les modules parcourent des ensembles de données volumineux. Vous voulez consacrer votre temps et votre intelligence à interpréter vos données, plutôt que de tâtonner minutieusement avec des outils moins puissants.

Mais j'ai entendu dire que les pandas sont lents…

Lorsque j'ai commencé à utiliser Pandas, on m'a dit que, même s'il s'agissait d'un excellent outil pour disséquer des données, Pandas était trop lent à utiliser comme outil de modélisation statistique. Au début, cela s'est avéré vrai. J'ai passé plus de quelques minutes à tourner mes pouces, attendant que les Pandas parcourent les données.

Mais ensuite, j'ai appris que Pandas est construit au-dessus de la structure du tableau NumPy, et tant de ses opérations sont effectuées en C, soit via NumPy, soit via les propreslibrary de Pandas de modules d'extension Python écrits en Cython et compilé en C. Alors, les Pandas ne devraient-ils pas être rapides aussi?

Il devrait absolument l'être, si vous l'utilisez comme il était prévu!

Le paradoxe est que ce qui pourrait autrement «ressembler» au codePythonic peut être sous-optimal dans Pandas en ce qui concerne l'efficacité. Comme NumPy,Pandas is designed for vectorized operations qui fonctionnent sur des colonnes entières ou des ensembles de données en un seul balayage. Penser à chaque «cellule» ou à chaque ligne individuellement devrait généralement être un dernier recours, pas un premier.

Ce tutoriel

Pour être clair, ce n'est pas un guide sur la façon de sur-optimiser votre code Pandas. Pandas est déjà conçu pour fonctionner rapidement s'il est utilisé correctement. De plus, il y a une grande différence entre l'optimisation et l'écriture de code propre.

Ceci est un guide d'utilisation de Pandas Pythonically pour tirer le meilleur parti de ses fonctionnalités intégrées puissantes et faciles à utiliser. De plus, vous apprendrez quelques conseils pratiques pour gagner du temps, de sorte que vous n'aurez pas à tourner ces pouces chaque fois que vous travaillez avec vos données.

Dans ce didacticiel, vous couvrirez les éléments suivants:

  • Avantages de l'utilisation des donnéesdatetime avec des séries chronologiques

  • La voie la plus efficace pour effectuer des calculs par lots

  • Gain de temps en stockant des données avec HDFStore

Pour illustrer ces sujets, je vais prendre un exemple de mon travail de jour qui examine une série chronologique de consommation d'électricité. Après avoir chargé les données, vous progresserez successivement par des moyens plus efficaces pour arriver au résultat final. Un adage qui vaut pour la plupart des pandas est qu'il y a plus d'une façon de passer de A à B. Cependant, cela ne signifie pas que toutes les options disponibles évolueront également vers des ensembles de données plus volumineux et plus exigeants.

En supposant que vous sachiez déjà comment faire quelquesdata selection in Pandas basiques, commençons.

La tâche à accomplir

Le but de cet exemple sera d'appliquer des tarifs énergétiques en fonction du temps d'utilisation pour trouver le coût total de la consommation d'énergie pour un an. Autrement dit, à différentes heures de la journée, le prix de l'électricité varie, la tâche consiste donc à multiplier l'électricité consommée pour chaque heure par le prix correct pour l'heure au cours de laquelle elle a été consommée.

Lisons nos données à partir d'unCSV file qui comporte deux colonnes: une pour la date et l'heure et une pour l'énergie électrique consommée en kilowattheures (kWh):

CSV data

Les lignes contiennent l'électricité utilisée dans chaque heure, il y a donc365 x 24 = 8760lignes pour toute l'année. Chaque ligne indique l'utilisation pour «l'heure de début» à l'heure, donc 1/1/13 0:00 indique l'utilisation pour la première heure du 1er janvier.

Gain de temps avec les données Datetime

La première chose que vous devez faire est de lire vos données à partir du fichier CSV avec l'une des fonctions d'E / S de Pandas:

>>>

>>> import pandas as pd
>>> pd.__version__
'0.23.1'

# Make sure that `demand_profile.csv` is in your
# current working directory.
>>> df = pd.read_csv('demand_profile.csv')
>>> df.head()
     date_time  energy_kwh
0  1/1/13 0:00       0.586
1  1/1/13 1:00       0.580
2  1/1/13 2:00       0.572
3  1/1/13 3:00       0.596
4  1/1/13 4:00       0.592

Cela semble correct à première vue, mais il y a un petit problème. Pandas et NumPy ont un concept dedtypes (types de données). Si aucun argument n'est spécifié,date_time prendra un dtypeobject:

>>>

>>> df.dtypes
date_time      object
energy_kwh    float64
dtype: object

>>> type(df.iat[0, 0])
str

Ce n'est pas idéal. object est un conteneur non seulement pourstr, mais pour toute colonne qui ne peut pas s'intégrer parfaitement dans un type de données. Il serait difficile et inefficace de travailler avec des dates sous forme de chaînes. (Il serait également inefficace en mémoire.)

Pour travailler avec des données de séries chronologiques, vous souhaiterez que la colonnedate_time soit mise en forme comme un tableau d'objets datetime. (Pandas appelle cela unTimestamp.) Pandas rend chaque étape assez simple ici:

>>>

>>> df['date_time'] = pd.to_datetime(df['date_time'])
>>> df['date_time'].dtype
datetime64[ns]

(Notez que vous pouvez également utiliser un PandasPeriodIndex dans ce cas.)

Vous avez maintenant un DataFrame appelédf qui ressemble beaucoup à notre fichier CSV. Il a deux colonnes et un index numérique pour référencer les lignes.

>>>

>>> df.head()
               date_time    energy_kwh
0    2013-01-01 00:00:00         0.586
1    2013-01-01 01:00:00         0.580
2    2013-01-01 02:00:00         0.572
3    2013-01-01 03:00:00         0.596
4    2013-01-01 04:00:00         0.592

Le code ci-dessus est simple et facile, mais à quelle vitesse? Mettons-le à l'épreuve en utilisant untiming decorator, que j'ai appelé de manière non originale@timeit. Ce décorateur imite en grande partietimeit.repeat() de la bibliothèque standard de Python, mais il vous permet de renvoyer le résultat de la fonction elle-même et d’imprimer son temps d’exécution moyen à partir de plusieurs essais. (Letimeit.repeat() de Python renvoie les résultats de synchronisation, pas le résultat de la fonction.)

Créer une fonction et placer le décorateur@timeit directement au-dessus signifie que chaque fois que la fonction est appelée, elle sera chronométrée. Le décorateur exécute une boucle extérieure et une boucle intérieure:

>>>

>>> @timeit(repeat=3, number=10)
... def convert(df, column_name):
...     return pd.to_datetime(df[column_name])

>>> # Read in again so that we have `object` dtype to start
>>> df['date_time'] = convert(df, 'date_time')
Best of 3 trials with 10 function calls per trial:
Function `convert` ran in average of 1.610 seconds.

Le résultat? 1.6 seconds for 8760 rows of data. "Génial", pourrait-on dire, "ce n'est pas du tout le temps." Mais que se passe-t-il si vous rencontrez des ensembles de données plus volumineux, par exemple, une année d'utilisation d'électricité à des intervalles d'une minute? Cela représente 60 fois plus de données, vous finirez donc par attendre environ une minute et demie. Cela commence à sembler moins tolérable.

En réalité, j'ai récemment analysé 10 ans de données horaires sur l'électricité provenant de 330 sites. Pensez-vous que j'ai attendu 88 minutes pour convertir les heures de données? Absolument pas!

Comment pouvez-vous accélérer cela? En règle générale, les pandas seront beaucoup plus rapides, moins ils auront à interpréter vos données. Dans ce cas, vous verrez des améliorations de vitesse énormes simplement en disant aux Pandas à quoi ressemblent vos données d'heure et de date, en utilisant le paramètre de format. Vous pouvez le faire en utilisant les codesstrftime trouvéshere et en les saisissant comme ceci:

>>>

>>> @timeit(repeat=3, number=100)
>>> def convert_with_format(df, column_name):
...     return pd.to_datetime(df[column_name],
...                           format='%d/%m/%y %H:%M')
Best of 3 trials with 100 function calls per trial:
Function `convert_with_format` ran in average of 0.032 seconds.

Le nouveau résultat? 0.032 seconds, which is 50 times faster! Vous venez donc d'économiser environ 86 minutes de temps de traitement pour mes 330 sites. Pas une mauvaise amélioration!

Un détail plus fin est que les datetimes dans le CSV ne sont pas enISO 8601 format: vous auriez besoin deYYYY-MM-DD HH:MM. Si vous ne spécifiez pas de format, Pandas utilisera le packagedateutil pour convertir chaque chaîne en une date.

Inversement, si les données datetime brutes sont déjà au format ISO 8601, les Pandas peuvent immédiatement prendre unfast route pour analyser les dates. C'est une des raisons pour lesquelles être explicite sur le format est si bénéfique ici. Une autre option est de passer le paramètreinfer_datetime_format=True, mais il est généralement avantageux d'être explicite.

Note:read_csv() de Pandas vous permet également d'analyser les dates dans le cadre de l'étape d'E / S de fichier. Voir les paramètresparse_dates,infer_datetime_format etdate_parser.

Boucle simple sur les données Pandas

Maintenant que vos dates et heures sont dans un format pratique, vous êtes prêt à vous lancer dans le calcul de vos coûts d'électricité. N'oubliez pas que le coût varie selon l'heure, vous devrez donc appliquer conditionnellement un facteur de coût à chaque heure de la journée. Dans cet exemple, les coûts de durée d'utilisation seront définis comme suit:

Type de tarif Cents par kWh Intervalle de temps

Peak

28

17h00 à 24h00

Épaule

20

7h00 à 17h00

Hors pointe

12

0h00 à 7h00

Si le prix était un plat de 28 cents par kWh pour chaque heure de la journée, la plupart des gens familiers avec les pandas sauraient que ce calcul pourrait être réalisé en une seule ligne:

>>>

>>> df['cost_cents'] = df['energy_kwh'] * 28

Cela se traduira par la création d'une nouvelle colonne avec le coût de l'électricité pour cette heure:

               date_time    energy_kwh       cost_cents
0    2013-01-01 00:00:00         0.586           16.408
1    2013-01-01 01:00:00         0.580           16.240
2    2013-01-01 02:00:00         0.572           16.016
3    2013-01-01 03:00:00         0.596           16.688
4    2013-01-01 04:00:00         0.592           16.576
# ...

Mais notre calcul des coûts dépend de l'heure de la journée. C'est là que vous verrez beaucoup de gens utiliser Pandas comme il n'était pas prévu: en écrivant une boucle pour faire le calcul conditionnel.

Pour le reste de ce didacticiel, vous partirez d'une solution de base moins qu'idéale et évoluerez vers une solution Pythonic qui exploite pleinement les Pandas.

Mais qu'est-ce que Pythonic dans le cas des Pandas? L'ironie est que ce sont ceux qui sont expérimentés dans d'autres langages de codage (moins conviviaux) tels que C ++ ou Java qui sont particulièrement sensibles à cela car ils «pensent en boucle».

Regardons unloop approach qui n'est pas pythonique et que beaucoup de gens prennent quand ils ne savent pas comment Pandas est conçu pour être utilisé. Nous utiliserons à nouveau@timeit pour voir à quelle vitesse cette approche est.

Commençons par créer une fonction pour appliquer le tarif approprié à une heure donnée:

def apply_tariff(kwh, hour):
    """Calculates cost of electricity for given hour."""
    if 0 <= hour < 7:
        rate = 12
    elif 7 <= hour < 17:
        rate = 20
    elif 17 <= hour < 24:
        rate = 28
    else:
        raise ValueError(f'Invalid hour: {hour}')
    return rate * kwh

Voici la boucle qui n'est pas Pythonique, dans toute sa splendeur:

>>>

>>> # NOTE: Don't do this!
>>> @timeit(repeat=3, number=100)
... def apply_tariff_loop(df):
...     """Calculate costs in loop.  Modifies `df` inplace."""
...     energy_cost_list = []
...     for i in range(len(df)):
...         # Get electricity used and hour of day
...         energy_used = df.iloc[i]['energy_kwh']
...         hour = df.iloc[i]['date_time'].hour
...         energy_cost = apply_tariff(energy_used, hour)
...         energy_cost_list.append(energy_cost)
...     df['cost_cents'] = energy_cost_list
...
>>> apply_tariff_loop(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_loop` ran in average of 3.152 seconds.

Pour les personnes qui ont ramassé Pandas après avoir écrit «pur Python» pendant un certain temps auparavant, cette conception peut sembler naturelle: vous avez un typique «pour chaquex, conditionnel ày, faitesz. »

Cependant, cette boucle est maladroite. Vous pouvez considérer ce qui précède comme un «contre-modèle» dans Pandas pour plusieurs raisons. Tout d'abord, il doit initialiser une liste dans laquelle les sorties seront enregistrées.

Deuxièmement, il utilise l'objet opaquerange(0, len(df)) pour boucler, puis après avoir appliquéapply_tariff(), il doit ajouter le résultat à une liste qui est utilisée pour créer la nouvelle colonne DataFrame. Il fait également ce que l'on appellechained indexing avecdf.iloc[i]['date_time'], ce qui conduit souvent à des résultats inattendus.

Mais le plus gros problème avec cette approche est le coût en temps des calculs. Sur ma machine, cette boucle a pris plus de 3 secondes pour 8760 lignes de données. Ensuite, vous examinerez quelques solutions améliorées pour l'itération sur les structures Pandas.

Bouclage avec.itertuples() et.iterrows()

Quelles autres approches pouvez-vous adopter? Eh bien, Pandas a rendu la syntaxe defor i in range(len(df)) redondante en introduisant les méthodesDataFrame.itertuples() etDataFrame.iterrows(). Ce sont deux méthodes génératrices quiyield une ligne à la fois.

.itertuples() renvoie unnamedtuple pour chaque ligne, avec la valeur d'index de la ligne comme premier élément du tuple. Unnametuple est une structure de données du modulecollections de Python qui se comporte comme un tuple Python mais dont les champs sont accessibles par recherche d’attributs.

.iterrows() renvoie des paires (tuples) de (index,Series) pour chaque ligne du DataFrame.

Bien que.itertuples() ait tendance à être un peu plus rapide, restons dans Pandas et utilisons.iterrows() dans cet exemple, car certains lecteurs n’ont peut-être pas rencontrénametuple. Voyons ce que cela permet:

>>>

>>> @timeit(repeat=3, number=100)
... def apply_tariff_iterrows(df):
...     energy_cost_list = []
...     for index, row in df.iterrows():
...         # Get electricity used and hour of day
...         energy_used = row['energy_kwh']
...         hour = row['date_time'].hour
...         # Append cost list
...         energy_cost = apply_tariff(energy_used, hour)
...         energy_cost_list.append(energy_cost)
...     df['cost_cents'] = energy_cost_list
...
>>> apply_tariff_iterrows(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_iterrows` ran in average of 0.713 seconds.

Des gains marginaux ont été réalisés. La syntaxe est plus explicite et il y a moins d'encombrement dans vos références de valeur de ligne, donc c'est plus lisible. En termes de gain de temps, c'est presque 5 cinq fois plus rapide!

Cependant, il y a plus de place pour l'amélioration. Vous utilisez toujours une certaine forme de boucle for Python, ce qui signifie que chaque appel de fonction est effectué en Python alors qu'il pourrait idéalement être fait dans un langage plus rapide intégré à l'architecture interne de Pandas.

Pandas '.apply()

Vous pouvez encore améliorer cette opération en utilisant la méthode.apply() au lieu de.iterrows(). La méthode.apply()de Pandas prend des fonctions (appelables) et les applique le long d'un axe d'un DataFrame (toutes les lignes ou toutes les colonnes). Dans cet exemple, unlambda function vous aidera à passer les deux colonnes de données dansapply_tariff():

>>>

>>> @timeit(repeat=3, number=100)
... def apply_tariff_withapply(df):
...     df['cost_cents'] = df.apply(
...         lambda row: apply_tariff(
...             kwh=row['energy_kwh'],
...             hour=row['date_time'].hour),
...         axis=1)
...
>>> apply_tariff_withapply(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_withapply` ran in average of 0.272 seconds.

Les avantages syntaxiques de.apply() sont évidents, avec une réduction significative du nombre de lignes et un code explicite très lisible. Dans ce cas, le temps pris était environ la moitié de celui de la méthode.iterrows().

Cependant, ce n’est pas encore «extrêmement rapide». Une des raisons est que.apply() essaiera en interne de boucler sur les itérateurs deCython. Mais dans ce cas, lelambda que vous avez passé n'est pas quelque chose qui peut être géré en Cython, donc il est appelé en Python, ce qui n'est donc pas si rapide.

Si vous deviez utiliser.apply() pour mes 10 années de données horaires pour 330 sites, vous envisageriez environ 15 minutes de temps de traitement. Si ce calcul devait être une petite partie d'un modèle plus grand, vous voudriez vraiment accélérer les choses. C’est là que les opérations vectorisées sont utiles.

Sélection de données avec.isin()

Plus tôt, vous avez vu que s'il y avait un prix de l'électricité unique, vous pourriez appliquer ce prix à toutes les données de consommation d'électricité dans une ligne de code (df['energy_kwh'] * 28). Cette opération particulière était un exemple d'une opération vectorisée, et c'est le moyen le plus rapide de faire les choses dans Pandas.

Mais comment pouvez-vous appliquer des calculs de conditions en tant qu'opérations vectorisées dans Pandas? Une astuce consiste à sélectionner et à grouper les parties du DataFrame en fonction de vos conditions, puis à appliquer une opération vectorisée à chaque groupe sélectionné.

Dans cet exemple suivant, vous verrez comment sélectionner des lignes avec la méthode.isin()de Pandas, puis appliquer le tarif approprié dans une opération vectorisée. Avant de faire cela, cela rendra les choses un peu plus pratiques si vous définissez la colonnedate_time comme index du DataFrame:

df.set_index('date_time', inplace=True)

@timeit(repeat=3, number=100)
def apply_tariff_isin(df):
    # Define hour range Boolean arrays
    peak_hours = df.index.hour.isin(range(17, 24))
    shoulder_hours = df.index.hour.isin(range(7, 17))
    off_peak_hours = df.index.hour.isin(range(0, 7))

    # Apply tariffs to hour ranges
    df.loc[peak_hours, 'cost_cents'] = df.loc[peak_hours, 'energy_kwh'] * 28
    df.loc[shoulder_hours,'cost_cents'] = df.loc[shoulder_hours, 'energy_kwh'] * 20
    df.loc[off_peak_hours,'cost_cents'] = df.loc[off_peak_hours, 'energy_kwh'] * 12

Voyons comment cela se compare:

>>>

>>> apply_tariff_isin(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_isin` ran in average of 0.010 seconds.

Pour comprendre ce qui se passe dans ce code, vous devez savoir que la méthode.isin() renvoie un tableau de valeurs booléennes qui ressemble à ceci:

[False, False, False, ..., True, True, True]

Ces valeurs identifient les indices DataFrame (datetimes) compris dans la plage horaire spécifiée. Ensuite, lorsque vous transmettez ces tableaux booléens à l’indexeur.loc du DataFrame, vous obtenez une tranche du DataFrame qui n’inclut que les lignes correspondant à ces heures. Après cela, il s'agit simplement de multiplier la tranche par le tarif approprié, ce qui est une opération vectorisée rapide.

Comment cela se compare-t-il à nos opérations de bouclage ci-dessus? Tout d'abord, vous remarquerez peut-être que vous n'avez plus besoin deapply_tariff(), car toute la logique conditionnelle est appliquée dans la sélection des lignes. Il y a donc une énorme réduction des lignes de code que vous devez écrire et du code Python qui est appelé.

Qu'en est-il du temps de traitement? 315 fois plus rapide que la boucle qui n’était pas Pythonic, environ 71 fois plus rapide que.iterrows() et 27 fois plus rapide que.apply(). Maintenant, vous vous déplacez à la vitesse dont vous avez besoin pour passer rapidement et facilement les grands ensembles de données.

Pouvons-nous faire mieux?

Dansapply_tariff_isin(), nous faisons encore certes du «travail manuel» en appelantdf.loc etdf.index.hour.isin() trois fois chacun. Vous pourriez faire valoir que cette solution n'est pas évolutive si nous avions une plage plus granuleuse de plages horaires. (Un taux différent pour chaque heure nécessiterait des appels de 24.isin().) Heureusement, vous pouvez faire les choses encore plus par programmation avec la fonctionpd.cut()de Pandas dans ce cas:

@timeit(repeat=3, number=100)
def apply_tariff_cut(df):
    cents_per_kwh = pd.cut(x=df.index.hour,
                           bins=[0, 7, 17, 24],
                           include_lowest=True,
                           labels=[12, 20, 28]).astype(int)
    df['cost_cents'] = cents_per_kwh * df['energy_kwh']

Prenons une seconde pour voir ce qui se passe ici. pd.cut() applique un tableau d'étiquettes (nos coûts) en fonction du bac auquel appartient chaque heure. Notez que le paramètreinclude_lowest indique si le premier intervalle doit être inclus à gauche ou non. (Vous souhaitez incluretime=0 dans un groupe.)

C'est un moyen entièrement vectorisé pour arriver au résultat souhaité, et il arrive en tête en termes de timing:

>>>

>>> apply_tariff_cut(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_cut` ran in average of 0.003 seconds.

Jusqu'à présent, vous avez accumulé plus d'une heure à moins d'une seconde pour traiter l'ensemble de données complet de 300 sites. Pas mal! Il existe une dernière option, cependant, qui consiste à utiliser les fonctions NumPy pour manipuler les tableaux NumPy sous-jacents pour chaque DataFrame, puis à réintégrer les résultats dans les structures de données Pandas.

N'oubliez pas NumPy!

Un point à ne pas oublier lorsque vous utilisez Pandas est que la série Pandas et les DataFrames sont conçus au-dessus de la bibliothèque NumPy. Cela vous donne encore plus de flexibilité de calcul, car Pandas fonctionne de manière transparente avec les tableaux et les opérations NumPy.

Dans ce cas suivant, vous utiliserez la fonctiondigitize() de NumPy. Il est similaire auxcut() de Pandas en ce sens que les données seront regroupées, mais cette fois, elles seront représentées par un tableau d'index représentant à quelle case appartient chaque heure. Ces indices sont ensuite appliqués à un tableau de prix:

@timeit(repeat=3, number=100)
def apply_tariff_digitize(df):
    prices = np.array([12, 20, 28])
    bins = np.digitize(df.index.hour.values, bins=[7, 17, 24])
    df['cost_cents'] = prices[bins] * df['energy_kwh'].values

Comme la fonctioncut(), cette syntaxe est merveilleusement concise et facile à lire. Mais comment se compare-t-il en vitesse? Voyons voir:

>>>

>>> apply_tariff_digitize(df)
Best of 3 trials with 100 function calls per trial:
Function `apply_tariff_digitize` ran in average of 0.002 seconds.

À ce stade, il y a encore une amélioration des performances, mais elle devient de plus en plus marginale. C'est probablement le bon moment pour appeler une journée sur le piratage de l'amélioration du code et penser à la situation dans son ensemble.

Avec Pandas, cela peut aider à maintenir la «hiérarchie», si vous voulez, des options préférées pour faire des calculs par lots comme vous l'avez fait ici. Ceux-ci seront généralement classés du plus rapide au plus lent (et du plus au moins flexible):

  1. Utilisez des opérations vectorisées: méthodes et fonctions Pandas sans boucles for.

  2. Utilisez la méthode.apply() avec un appelable.

  3. Utilisez.itertuples(): itérer sur les lignes DataFrame en tant quenamedtuples du modulecollections de Python.

  4. Utilisez.iterrows(): itérer sur les lignes DataFrame sous forme de paires (index,pd.Series). Bien qu'une série Pandas soit une structure de données flexible, il peut être coûteux de construire chaque ligne dans une série, puis d'y accéder.

  5. Utilisez «élément par élément» pour les boucles, en mettant à jour chaque cellule ou ligne une à la fois avecdf.loc oudf.iloc. (Ou,.at /.iat pour un accès scalaire rapide.)

Don’t Take My Word For It: L'ordre de priorité ci-dessus est une suggestionstraight from a core Pandas developer.

Voici «l'ordre de priorité» ci-dessus au travail, avec chaque fonction que vous avez créée ici:

Une fonction Durée d'exécution (secondes)

apply_tariff_loop()

3.152

apply_tariff_iterrows()

0.713

apply_tariff_withapply()

0.272

apply_tariff_isin()

0.010

apply_tariff_cut()

0.003

apply_tariff_digitize()

0.002

Empêcher le retraitement avec HDFStore

Maintenant que vous avez examiné les processus de données rapides dans Pandas, explorons comment éviter complètement le temps de retraitement avecHDFStore, qui a récemment été intégré à Pandas.

Souvent, lorsque vous créez un modèle de données complexe, il est pratique d'effectuer un prétraitement de vos données. Par exemple, si vous disposiez de 10 ans de données de consommation d'électricité à fréquence minute, la simple conversion de la date et de l'heure en datetime peut prendre 20 minutes, même si vous spécifiez le paramètre de format. Vous ne voulez vraiment avoir à le faire qu'une seule fois, pas à chaque fois que vous exécutez votre modèle, pour des tests ou des analyses.

Une chose très utile que vous pouvez faire ici est de prétraiter puis de stocker vos données sous leur forme traitée pour les utiliser en cas de besoin. Mais comment pouvez-vous stocker les données dans le bon format sans avoir à les retraiter à nouveau? Si vous deviez enregistrer au format CSV, vous perdriez simplement vos objets datetime et devrez le retraiter lors d'un nouvel accès.

Pandas a une solution intégrée pour cela qui utiliseHDF5, un format de stockage haute performance conçu spécifiquement pour stocker des tableaux tabulaires de données. La classeHDFStorede Pandas vous permet de stocker votre DataFrame dans un fichier HDF5 afin de pouvoir y accéder efficacement, tout en conservant les types de colonnes et autres métadonnées. C'est une classe de type dictionnaire, vous pouvez donc lire et écrire comme vous le feriez pour un objet Pythondict.

Voici comment procéder pour stocker votre DataFrame de consommation électrique prétraitée,df, dans un fichier HDF5:

# Create storage object with filename `processed_data`
data_store = pd.HDFStore('processed_data.h5')

# Put DataFrame into the object setting the key as 'preprocessed_df'
data_store['preprocessed_df'] = df
data_store.close()

Vous pouvez maintenant éteindre votre ordinateur et faire une pause en sachant que vous pouvez revenir et que vos données traitées vous attendront lorsque vous en aurez besoin. Aucun retraitement requis. Voici comment accéder à vos données à partir du fichier HDF5, avec des types de données préservés:

# Access data store
data_store = pd.HDFStore('processed_data.h5')

# Retrieve data using key
preprocessed_df = data_store['preprocessed_df']
data_store.close()

Un magasin de données peut héberger plusieurs tables, avec le nom de chacune comme clé.

Juste une note sur l'utilisation de HDFStore dans Pandas: vous devrez avoir PyTables> = 3.0.0 installé, donc après avoir installé Pandas, assurez-vous de mettre à jour PyTables comme ceci:

pip install --upgrade tables

Conclusions

Si vous n’avez pas l’impression que votre projet Pandas estfast,flexible,easy etintuitive, pensez à repenser la façon dont vous utilisez la bibliothèque.

Les exemples que vous avez explorés ici sont assez simples mais illustrent comment la bonne application des fonctionnalités de Pandas peut améliorer considérablement l'exécution et la lisibilité du code au démarrage. Voici quelques règles générales que vous pourrez appliquer la prochaine fois que vous travaillerez avec de grands ensembles de données dans Pandas:

  • Essayez d'utiliservectorized operations dans la mesure du possible plutôt que d'aborder les problèmes avec la mentalitéfor x in df.... Si votre code contient de nombreuses boucles for, il pourrait être mieux adapté pour travailler avec des structures de données Python natives, car Pandas est livré avec beaucoup de surcharge.

  • Si vous avez des opérations plus complexes où la vectorisation est simplement impossible ou trop difficile à travailler efficacement, utilisez la méthode.apply().

  • Si vous devez faire une boucle sur votre tableau (ce qui arrive), utilisez.iterrows() ou.itertuples() pour améliorer la vitesse et la syntaxe.

  • Les pandas ont beaucoup d'options, et il y a presque toujours plusieurs façons de passer de A à B. Soyez conscient de cela, comparez les performances des différents itinéraires et choisissez celui qui fonctionne le mieux dans le contexte de votre projet.

  • Une fois que vous avez créé un script de nettoyage des données, évitez le retraitement en stockant vos résultats intermédiaires avec HDFStore.

  • L'intégration de NumPy dans les opérations Pandas peut souvent améliorer la vitesse et simplifier la syntaxe.