un petit TP pour travailler
le chargement et la sélection
un peu de groupby
un peu de gestion du temps et des durées
import pandas as pdles données¶
On va étudier un jeu de données trouvé sur Internet
# 2024: le site original semble être *down*
# URL = "http://www.xavierdupre.fr/enseignement/complements/marathon.txt"
DATA = "data/marathon.txt"# regardons les 5 premières lignes du fichier de données
# (ou bien ouvrez-le dans vs-code)
with open(DATA) as f:
for _ in range(5):
print(next(f), end="")PARIS 2011 02:06:29 7589
PARIS 2010 02:06:41 7601
PARIS 2009 02:05:47 7547
PARIS 2008 02:06:40 7600
PARIS 2007 02:07:17 7637
chargement¶
Le premier réflexe pour charger un fichier de ce genre, c’est d’utiliser la fonction read_csv de pandas
# votre cellule de code
# qu'on va faire descendre
# et raffiner au fur et à mesure
df0 = pd.read_csv(DATA)
df0.head()c’est un début, mais ça ne marche pas franchement bien !
il faut donc bien regarder la doc
# pd.read_csv?et pour commencer je vous invite à préciser le séparateur:
# à vous de modifier cette première approche
df1 = pd.read_csv(DATA)# pour vérifier, ceci doit afficher True
df1.shape == (358, 4) and df1.iloc[0, 0] == 'PARIS' and df1.columns[0] == 'PARIS'Falsec’est mieux, mais les noms des colonnes ne sont pas corrects
en effet par défaut, read_csv utilise la première ligne pour déterminer les noms des colonnes
or dans le fichier texte il n’y a pas le nom des colonnes ! (voyez ci-dessus)
du coup ce serait pertinent de donner un nom aux colonnes
NAMES = ["city", "year", "duration", "seconds"]# à vous de créer une donnée bien propre
df = ...# pour vérifier, ceci doit afficher True
df.shape == (359, 4) and df.iloc[0, 0] == 'PARIS' and df.columns[0] == 'city'---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[10], line 3
1 # pour vérifier, ceci doit afficher True
----> 3 df.shape == (359, 4) and df.iloc[0, 0] == 'PARIS' and df.columns[0] == 'city'
AttributeError: 'ellipsis' object has no attribute 'shape'# ce qui maintenant nous donne ceci
df.head(2)---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[11], line 3
1 # ce qui maintenant nous donne ceci
----> 3 df.head(2)
AttributeError: 'ellipsis' object has no attribute 'head'sauvegarde dans un fichier csv¶
dans l’autre sens, quand on a produit une dataframe et qu’on veut sauver le résultat dans un fichier texte
# df.to_csv?par exemple je crée ici un fichier qu’on peut relire sous excel
loop = "marathon-loop.csv"
df.to_csv(loop, sep=";", index=False)---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[13], line 2
1 loop = "marathon-loop.csv"
----> 2 df.to_csv(loop, sep=";", index=False)
AttributeError: 'ellipsis' object has no attribute 'to_csv'# pour voir un aperçu
# nouveau vous pouvez regarder le fichier avec vs-code
# ou encore dans le terminal avec $ less marathon-loop.csv (sortir avec 'q')
%cat marathon-loop.csvcat: marathon-loop.csv: No such file or directory
des recherches¶
les éditions de 1971¶
# à vous de calculer les éditions de 1971
df_1971 = ...# ceci doit retourner True
df_1971.shape == (3, 4) and df_1971.seconds.max() == 8574---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[16], line 3
1 # ceci doit retourner True
----> 3 df_1971.shape == (3, 4) and df_1971.seconds.max() == 8574
AttributeError: 'ellipsis' object has no attribute 'shape'l’édition de 1981 à Londres¶
# à vous
df_london_1981 = ...# ceci doit retourner True
df_london_1981.shape == (1, 4) and df_london_1981.iloc[0].seconds == 7908---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[18], line 3
1 # ceci doit retourner True
----> 3 df_london_1981.shape == (1, 4) and df_london_1981.iloc[0].seconds == 7908
AttributeError: 'ellipsis' object has no attribute 'shape'trouver toutes les villes¶
on veut construire une collection de toutes les villes qui apparaissent au moins une fois
# à vous
cities = ...intéressez-vous au type du résultat (dataframe, series, ndarray, liste ?)
des extraits¶
attention ici dans les consignes, les numéros de ligne commencent à 1
extrait #1¶
les entrées correspondant aux lignes 10 à 12 inclusivement
# à vous
df_10_to_12 = ...# ceci doit retourner True
df_10_to_12.shape == (3, 4) and df_10_to_12.iloc[0].year == 2002 and df_10_to_12.iloc[-1].year == 2000---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[21], line 3
1 # ceci doit retourner True
----> 3 df_10_to_12.shape == (3, 4) and df_10_to_12.iloc[0].year == 2002 and df_10_to_12.iloc[-1].year == 2000
AttributeError: 'ellipsis' object has no attribute 'shape'extrait #2¶
une Series correspondant aux événements à Paris après 2000 (inclus), dans laquelle on n’a gardé que l’année
# à vous
s_paris_2000 = ...s_paris_2000Ellipsis# ceci doit retourner True
isinstance(s_paris_2000, pd.Series) and len(s_paris_2000) == 12 and s_paris_2000.iloc[-1] == 2000Falseextrait #3¶
une DataFrame correspondant aux événements à Paris après 2000,
dans laquelle on n’a gardé que les deux colonnes year et seconds
df_paris_2000_ys = ...# ceci doit retourner True
(isinstance(df_paris_2000_ys, pd.DataFrame)
and df_paris_2000_ys.shape == (12, 2)
and df_paris_2000_ys.iloc[-2].seconds == 7780)Falseaggrégats¶
moyenne¶
ce serait quoi la moyenne de la colonne seconds ?
# calculer la moyenne de la colonne 'seconds'
seconds_average = ...# pour vérifier
import math
math.isclose(seconds_average, 7933.660167130919)---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[28], line 4
1 # pour vérifier
3 import math
----> 4 math.isclose(seconds_average, 7933.660167130919)
TypeError: must be real number, not ellipsiscombien de marathons par an¶
si maintenant je veux produire une série qui compte par année combien il y a eu de marathons
il y a plein de façons de faire, si vous en voyez plusieurs n’hésitez pas...
# à vous
count_by_year = ...# pour vérifier
(isinstance(count_by_year, pd.Series)
and len(count_by_year) == 65
and count_by_year.loc[1947] == 1
and count_by_year.loc[2007] == 9
and count_by_year.loc[2011] == 5)Falseles durées¶
dans cette partie, notre but est de simplement vérifier que la colonne seconds contient bien le nombre de secondes correspondant à la colonne duration
pour cela on va commencer par convertir la colonne duration en quelque chose d’un peu plus utilisable
numpy expose deux types particulièrement bien adaptés à la gestion du temps
datetime64pour modéliser un instant particuliertimedelta64pour modéliser une durée entre deux instants
voir plus de détails si nécessaire ici: https://
read_csv(parse_dates=)¶
commençons par écarter une fausse bonne idée
dans read_csv il y a une option parse_dates; mais regardez ce que ça donne
df_broken = pd.read_csv(
DATA, sep='\t',
names=['city', 'year', 'duration', 'seconds'],
parse_dates=['duration'])
df_brokença ne va pas !
le truc c’est que ici, on n’a pas une date, ce que nous avons c’est une durée
pd.to_timedelta()¶
# repartons des données de départ
df = pd.read_csv(DATA, sep="\t", names=NAMES)
df.dtypescity object
year int64
duration object
seconds int64
dtype: objectnon, pour convertir la colonne en datetime64 on va utiliser pd.to_timedelta()
voyez la documentation de cette fonction, et modifiez la dataframe df pour que la colonne duration soit maintenant du type timedelta64
# à vous# pour vérifier - doit retourner True
df.duration.dtype == 'timedelta64[ns]'False# et maintenant ça devrait être beaucoup mieux
df.head(2)# une fois que vous avez bien converti vous pourrez faire ceci
# df.duration.dt.componentsduration == seconds ?¶
à présent qu’on a converti duration dans le bon type, on peut utiliser toutes les fonctions disponibles sur ce type.
en pratique ça se fait en deux temps
sur l’objet
Serieson applique l’attributdtpour, en quelque sorte, se projeter dans l’espace des ‘date-time’
c’est exactement comme on l’a vu déjà avec le.strlorsqu’on a eu besoin d’appliquer des méthodes comme.lower()oureplace()sur les chaines et non pas sur la série
plus de détails ici https://pandas .pydata .org /docs /reference /api /pandas .Series .dt .html de là on peut appeler toutes les méthodes disponibles sur les objets
timedelta- on pourra en particulier s’intéresser àtotal_seconds
du coup pour vérifier que la colonne seconds correspond bien à duration, on écrirait quoi comme code (qui doit afficher True)
# à vouscolonnes hour minute et second¶
on se propose maintenant de rajouter des colonnes hour minute et second - qui doivent être de type entier
pour cela deux approches:
“à la main”: on fait les calculs nous-mêmes
après quoi on découvre par hasard dans une question SO que c’est disponible directement dans la colonne
duration- mais c’est bien caché...
à la main¶
indices
on peut calculer le quotient et le reste entre deux objets de type “durée” avec les opérateurs usuels
//et%
# par exemple
import numpy as np
# une durée de 1h
one_hour = np.timedelta64(1, 'h')
# guess what...
one_minute = np.timedelta64(1, 'm')
one_second = np.timedelta64(1, 's')
# une durée de 2h25
random_duration = 2*one_hour + np.timedelta64(25, 'm')
# eh bien on peut faire comme avec des entiers
quotient, reste = random_duration // one_hour, random_duration % one_hour
quotient, reste(np.int64(2), np.timedelta64(25,'m'))maintenant qu’on sait faire tout ça, on peut calculer les colonnes hour, minute et second
# à vous# pour vérifier, vous décommentez tout ceci et ça doit afficher True
# ( np.all(df.loc[0, ['hour', 'minute', 'second']] == [2, 6, 29])
# and df.hour.dtype == int
# and df.minute.dtype == int
# and df.second.dtype == int)version paresseuse avec dt.components¶
il se trouve qu’on peut faire le même travail sans s’embêter autant, une fois qu’on découvre que l’accesseur .dt possède un attribut qui donne accès à ce genre de détails
# on défait le travail de la section précédente, si nécessaire
for col in 'hour', 'minute', 'second':
if col in df.columns:
df.drop(columns=col, inplace=True)# à vous# pour vérifier: même consigne
# ( np.all(df.loc[0, ['hour', 'minute', 'second']] == [2, 6, 29])
# and df.hour.dtype == int
# and df.minute.dtype == int
# and df.second.dtype == int)