Skip to article frontmatterSkip to article content

un petit TP pour travailler

import pandas as pd

les 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()
Loading...

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'
False

c’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.csv
cat: 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_2000
Ellipsis
# ceci doit retourner True

isinstance(s_paris_2000, pd.Series) and len(s_paris_2000) == 12 and s_paris_2000.iloc[-1] == 2000
False

extrait #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)
False

aggré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 ellipsis

combien 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)
False

les 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

voir plus de détails si nécessaire ici: https://numpy.org/doc/stable/reference/arrays.datetime.html

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
Loading...

ç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.dtypes
city object year int64 duration object seconds int64 dtype: object

non, 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)
Loading...
# une fois que vous avez bien converti vous pourrez faire ceci
# df.duration.dt.components

duration == 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

du coup pour vérifier que la colonne seconds correspond bien à duration, on écrirait quoi comme code (qui doit afficher True)

# à vous

colonnes 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

indices

# 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)