Python
¶Python
est un langage de programmation en pleine croissance dans le domaine de la Data Science et du Big Data. Il a une syntaxe particulière pour certains aspects, avec des points communs tout de même avec de nombreux autres. Nous allons ici aborder les éléments suivants sur ce langage :
python
est un langage scripté, dont l'exécution se fait dans une console. Dans celle-ci, il est donc possible d'exécuter les commandes les unes après les autres. Il est aussi possible (et recommander) d'écrire son script dans un fichier texte (souvent avec l'extension .py
) et de l'exécuter via execfile()
.
Pour accéder à l'aide d'une fonction, il existe la fonction help()
, prenant éventuellement en paramètre une fonction directement, ou une chaîne de caractère. Si la fonction n'a pas de paramètre, elle démarre l'aide interactive.
Le module jupyter
permet de travailler avec des notebooks, qui sont des documents contenant à la fois le code, les résultat et du texte. Ce document est ainsi un notebook.
Comme tous les autres langages, python
a plusieurs types de base possibles pour les données. En voici quelques uns. Vous pouvez exécuter les commandes dans une console pour voir le résultat.
type | valeur | type() | commentaires |
---|---|---|---|
entier | 1 | int | |
réel | 1.234 | float | |
chaîne de caractères | "un mot" | str | on peut utiliser indifférement "" ou '' |
liste | [1, 2] | list | chaque élément peut être différent des autres |
tuple | (1, 2) | tuple | similaire à une liste mais constante |
dictionnaire | {"a": 1, "b": "deux"} | dict | ensemble de valeurs quelconques nommées |
Les tuples
, lists
et les dicts
peuvent s'imbriquer les uns dans les autres.
(1, (2, 3), [4, 5], {"a": 1, "b": "deux"})
[1, [2, 3], (4, 5), {"a": 1, "b": "deux"}]
{"a": 1, "b": "deux", "c": (5, 6), "d": [7, 8]}
Il existe des fonctions permettant de passer d'un type à l'autre (quand cela est possible), telles que int()
, float()
, str()
, tuple()
et list()
.
Il existe aussi des valeurs prédéfinies, telles que True
(vrai), False
(faux) et None
(donnée absente).
Il n'y a pas de mot-clé pour la définition d'une variable. Celle-ci est définie/créée lors de sa première affectation. Si elle n'existe pas mais qu'on essaie de l'utiliser, alors un message d'erreur apparaît. Il est aussi possible de la supprimer via la fonction del()
.
Bien que python
soit rigoureux dans l'évaluation des expressions (il ne fait pas de cast
automatique - i.e. changement de type des données), le type d'une variable est dit dynamique. Le type d'une variable dépend uniquement de la valeur de son affectation.
Comme vu précédemment, il existe la fonction print()
permettant d'afficher du texte et/ou le contenu des variables dans la console. Celle-ci peut prendre les paramètres sep
, qui permet d'indiquer le ou les caractères séparant les champs (un espace " "
par défaut), et end
, qui permet d'indiquer le caractère de fin de ligne (retour à la ligne "\n"
par défaut).
Il existe bien évidemment tous les opérateurs classiques, tels que présentés ci-dessous.
Type | Opérateurs |
---|---|
Arithmétiques | + - * / // % ** |
Comparaisons | > >= < <= == != |
Booléens | | & not() |
Comme dans tout langage, le traitement conditionnel se fait à partir d'un if
. Une particularité de python
est d'utiliser l'indentation (i.e. le décalage à droite à l'aide d'au moins une tabulation) pour définir les opérations à réaliser dans un bloc. Les clauses elif
et else
sont bien évidemment optionnelles.
if condition:
...
elif condition:
...
else:
...
On utilise en premier la boucle for
dans laquelle on peut utiliser la fonction range()
pour avoir les valeurs entre 0
(par défaut) et la valeur passée en paramètre - 1. Pour information, i
est persistant à la boucle et garde la dernière valeur.
for i in range(5):
print(i)
La fonction range()
peut prendre deux ou trois paramètres, et dans ce cas, génère la boucle entre les deux par pas de 1 (ou autre selon le troisième paramètres - celui-ci doit être cohérent par rapport aux deux premières valeurs).
Il est aussi possible d'utiliser :
list
ou un tuple
pour définir les valeurs à prendre ;dict
, l'itérateur prenant les valeurs des champs de celui-ci ;enumerate()
sur une liste, permettant de récupérer 2 valeurs (dont 2 variables à mettre à gauche), qui seront l'indice de la position et la valeur ;zip()
permettant de combiner 2 listes (élément par élément), et retournant elle aussi 2 valeurs.Enfin, on dispose aussi de la boucle while
qui teste en début de boucle si une condition est toujours vérifiée.
while condition:
...
Comme indiqué, il existe différents types d'objets en python
. Sont présentés ici des exemples de créations et de manipulations de chaînes (str
), de tuples
, de list
et de dictionnaires (dict
).
Une chaîne de caractère se définit à l'aide des quotes simples (''
) ou doubles (""
). Par défaut, python
présentera les chaînes avec des simples quotes. Mais en présence d'une apostrophe dans la chaîne, il faut la déclarer avec des doubles quotes. Il est possible de connaître la longueur de la chaîne avec la fonction len()
.
Sur ces chaînes, on peut réaliser un certain nombre d'opérations classiques, telles que le changement de casse (upper()
ou lower()
), la mise en majuscule des premières lettres de chaque mot (capitalize()
), la recherche d'une sous-chaîne (find()
- première occurence), le remplacement d'une sous-chaîne (replace()
), le dénombrement de sous-chaînes (count()
) ou le découpage en sous-chaînes selon un caractère (split()
).
Un tuple
en python
est un ensemble déclaré via des ()
, composé de valeurs pas forcément de même type et éventuellement complexe, qu'il n'est pas possible de modifier. C'est une constante, une fois déclarée.
Une list
est aussi un ensemble déclarée via des []
, composé d'éléments pas forcément tous du même type et possiblement complexe. A la différence d'un tuple, une liste est modifiable.
Nous disposons sur ces listes de plusieurs fonctions tels que reverse()
(pour inverser la liste), sort()
(tri, avec l'option reverse
pour le choix du tri), pop()
(pour récupérer et supprimer le dernier élément), append()
(pour ajouter un élément à la fin), insert()
(pour insérer un élément dans la liste, à la position indiquée - paramètres = position suivie de la valeur), remove()
(pour supprimer les valeurs passées en paramètre). Toutes ces fonctions modifient directement la liste sur laquelle on les applique.
Un autre moyen d'insérer une valeur, voire plusieurs, à une liste est d'utiliser l'opérateur +
, tel qu'indiqué ci-dessous. Celui-ci permet une concaténation des deux listes en une seule. L'opérateur *
permet lui de répéter une liste autant de fois que désiré.
Par contre, il faut faire très attention au passage de référence lorsqu'on copie une liste. En effet, dans le code suivant, on copie a
dans b
. Et en modifiant a
, on remarque que b
est aussi modifié. Et l'inverse est aussi vrai. Pour remédier à ce problème, on doit dupliquer la liste avec la fonction copy()
de la liste initiale.
Les dictionnaires (dict
en python
) sont des listes nommées (définies via des {}
), c'est-à-dire que chaque élément a un nom (appelé aussi clé). Ces éléments ne sont pas forcément tous du même type, et peuvent aussi être complexe. Ils sont similaires (pour ne pas dire identique) à des littéraux JSON.
Pour accéder aux élements d'un dictionnaire, on utlise le nom du champs dont on veut la valeur entre []
. Il existe aussi des fonctions utiles sur ces objets, telles que get()
(pour récupérer la valeur d'une clé), keys()
(pour avoir la liste des clés de l'objet), values()
(pour avoir les valeurs des clés, dans le même ordre que listé dans keys()
), popitem()
(pour récupérer un dictionnaire avec le dernier item, et le supprimer du dictionnaire initiale) et pop()
(pour récupérer la valeur de l'item passé en paramètre, et le supprimer de l'élément de départ).
On peut ajouter facilement un item à un dictionnaire, en lui affectant simplement une valeur.
De même que pour les listes, il faut faire attention lors de l'affectation d'un dictionnaire à un autre. La fonction copy()
permet donc d'obtenir une copie indépendante de l'objet initial.
Pour indexer une liste, un tuple, voire une chaîne de caractères, nous passons entre []
les valeurs de la position, avec plusieurs possibilités :
a[p]
: élément de a
à la position p
(pour rappel, 1ère position = 0)a[:p]
: tous les élements de 0 à p-1
a[p:]
: tous les élements de p
jusqu'à la fina[p:q]
: tous les éléments de p
à q-1
a[:]
ou a[::]
: tous les élementsa[::-1]
: tous les éléments dans l'ordre inversea[p:q:r]
: les élements de p
à q
par pas r
(celui-ci peut être négatif : dans ce cas q
< p
)On peut utiliser un mécanisme spécifique, appelé list comprehension (fonctionnant aussi sur les chaînes et les tuples), permettant de récupérer les valeurs (ou un calcul sur chaque valeur) pour tous les éléments de la liste (ou certains si on applique un if
).
a = [3, 1, 9, 7]
b = [x**2 for x in a]
print('b = ', b)
c = [x**2 for x in a if x >= 4]
print('c = ', c)
b = [9, 1, 81, 49] c = [81, 49]
Le mécanisme de list comprehension est aussi utilisable pour créer un dictionnaire. Il faut dans ce cas indiquer deux valeus : la clé et sa valeur. Dans notre cas, la fonction dict()
appliqué sur le résultat de la fonction zip()
des deux listes nous permet d'avoir le même résultat.
fruits = ["pommes", "bananes", "poires", "oranges"]
nombres = [5, 2, 10, 4]
d1 = {fruits[i]:nombres[i] for i in range(4)}
print('d1 = ', d1)
d2 = dict(zip(fruits, nombres))
print('d2 = ', d2)
d1 = {'pommes': 5, 'bananes': 2, 'poires': 10, 'oranges': 4} d2 = {'pommes': 5, 'bananes': 2, 'poires': 10, 'oranges': 4}
L'opérateur def
permet de créer une fonction (ou une procédure qui sera juste une fonction ne renvoyant rien). L'opérateur return
indiquant le résultat à renvoyer le cas échéant. Comme pour un if
, le bloc d'instructions est défini selon l'indentation.
Il est bien évidemment possible de passer un paramètre à une fonction, sans qu'on ait à déclarer son type.
Lorqu'il y a plus d'un paramètre, on peut faire un appel classique. Mais il est aussi possible de nommer explicitement les paramètres. Avec ce mécanisme, il est ainsi possible de les déclarer dans l'ordre que l'on veut. Mais si l'on nomme un paramètre, il est obligatoire de nommer les autres (erreur d'exécution sinon).
Il existe la possibilité de définir une valeur par défaut à un paramètre dans une fonction. Ceci permet d'appeler la fonction sans donner de valeur pour ce paramètre (la fonction utilisera la valeur par défaut donc).
def f(...):
...
return ...
Il est possible d'ajouter une condition if
dans la fonction lambda
avec un formalisme de type valeurTrue if condition else valeurFalse
. Ci-après, nous calculons le carré de chaque valeur, multiplié par -1
pour celles inférieur ou égale à 8.
b = map(lambda v: v **2 if v > 8 else -(v ** 2), a)
print(list(b))
[-9, -1, 81, -49]
Nous allons utiliser dans ce TP le module pandas
permettant la gestion de données avec Python dans un format (individus décrits par des variables) plus classique pour les méthodes statistiques.
pandas
¶Nous allons travailler sur les données tips. Vous pouvez trouver des informations (ici). Voici comment lire ces données dans python
avec read_csv()
de pandas
.
import pandas
# Lecture d'un fichier texte
tips = pandas.read_csv("tips.csv", header = 0, sep = ",")
tips.head()
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
Sur ces données, il est bien évidemment possible de voir quelques informations classiques.
# Type de la variable
type(tips)
pandas.core.frame.DataFrame
# nombre de lignes et de colonnes de la table
tips.shape
(244, 7)
# nombre de valeurs présentes dans chaque colonne
tips.count()
total_bill 244 tip 244 sex 244 smoker 244 day 244 time 244 size 244 dtype: int64
# détail complet de la table
tips.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 244 entries, 0 to 243 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 total_bill 244 non-null float64 1 tip 244 non-null float64 2 sex 244 non-null object 3 smoker 244 non-null object 4 day 244 non-null object 5 time 244 non-null object 6 size 244 non-null int64 dtypes: float64(2), int64(1), object(4) memory usage: 13.5+ KB
# nom des colonnes (au format liste)
list(tips.columns)
['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size']
# idem
list(tips)
['total_bill', 'tip', 'sex', 'smoker', 'day', 'time', 'size']
Une fois qu'on a des données, la première chose qu'on souhaite savoir faire souvent, est de pouvoir manipuler ces données; C'est-à-dire réaliser les opérations classiques en bases de données, à savoir :
Il y a bien évidmement d'autres opérations possibles, spécifiques ou non à Python.
Note Bene : certaines fonctions renvoient un nouvel objet qu'il faudra donc stocker dans une variable (nouvelle ou la même). Par contre, d'autres fonctions modifient directement l'objet en question.
Première étape essentielle, cela consiste à sélectionner un certain nombre de lignes de la table, selon une condition sur les valeurs des variables. Il exite pour cela la fonction query()
prenant en paramètre une chaîne de caractères contenant la condition à appliquer. Voici quelques exemples de condition. Il y a bien évidemment beaucoup d'autres fonctions existantes, en particuliers pour les chaînes.
tips.query('total_bill > 48') # que les factures de plus de 48$
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
59 | 48.27 | 6.73 | Male | No | Sat | Dinner | 4 |
156 | 48.17 | 5.00 | Male | No | Sun | Dinner | 6 |
170 | 50.81 | 10.00 | Male | Yes | Sat | Dinner | 3 |
212 | 48.33 | 9.00 | Male | No | Sat | Dinner | 4 |
tips.query('day in ("Sat", "Sun")') # que les factures ayant eu lieu un samedi ou un dimanche
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
... | ... | ... | ... | ... | ... | ... | ... |
238 | 35.83 | 4.67 | Female | No | Sat | Dinner | 3 |
239 | 29.03 | 5.92 | Male | No | Sat | Dinner | 3 |
240 | 27.18 | 2.00 | Female | Yes | Sat | Dinner | 2 |
241 | 22.67 | 2.00 | Male | Yes | Sat | Dinner | 2 |
242 | 17.82 | 1.75 | Male | No | Sat | Dinner | 2 |
163 rows × 7 columns
tips.query('size > 4 & sex == "Male"') # que les tables de plus de 4 convives et payées par un homme
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
141 | 34.30 | 6.7 | Male | No | Thur | Lunch | 6 |
142 | 41.19 | 5.0 | Male | No | Thur | Lunch | 5 |
156 | 48.17 | 5.0 | Male | No | Sun | Dinner | 6 |
185 | 20.69 | 5.0 | Male | No | Sun | Dinner | 5 |
187 | 30.46 | 2.0 | Male | Yes | Sun | Dinner | 5 |
216 | 28.15 | 3.0 | Male | Yes | Sat | Dinner | 5 |
Deuxième étape, celle-ci consiste tout simple à sélectionner certaines colonnes de la tables. En python, on utilise la fonction filter()
. Il faut noter que celle-ci peut aussi fonctionner sur les lignes (quand celles-ci ont un index - pas vu ici).
tips.filter(["sex", "total_bill"])
sex | total_bill | |
---|---|---|
0 | Female | 16.99 |
1 | Male | 10.34 |
2 | Male | 21.01 |
3 | Male | 23.68 |
4 | Female | 24.59 |
... | ... | ... |
239 | Male | 29.03 |
240 | Female | 27.18 |
241 | Male | 22.67 |
242 | Male | 17.82 |
243 | Female | 18.78 |
244 rows × 2 columns
Quand on fait ce genre d'opérations, il est courant que nous nous retrouvions avec des lignes identiques. Pour supprimer les doublons, nous utilisons la fonction drop_duplicates()
sur le résultat.
tips.filter(["sex", "smoker"]).drop_duplicates()
sex | smoker | |
---|---|---|
0 | Female | No |
1 | Male | No |
56 | Male | Yes |
67 | Female | Yes |
On peut aussi utiliser le formalisme dataframe.colonne
pour accéder aux valeurs d'une seule colonne
tips.total_bill
0 16.99 1 10.34 2 21.01 3 23.68 4 24.59 ... 239 29.03 240 27.18 241 22.67 242 17.82 243 18.78 Name: total_bill, Length: 244, dtype: float64
Pour le tri, il existe la fonction sort_values()
, dans laquelle on indique la ou les variables à utiliser pour le tri. Si on veut un tri descendant, on modifie la valeur du paramètre ascending
(True
par défaut).
tips.sort_values(by = "total_bill")
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
67 | 3.07 | 1.00 | Female | Yes | Sat | Dinner | 1 |
92 | 5.75 | 1.00 | Female | Yes | Fri | Dinner | 2 |
111 | 7.25 | 1.00 | Female | No | Sat | Dinner | 1 |
172 | 7.25 | 5.15 | Male | Yes | Sun | Dinner | 2 |
149 | 7.51 | 2.00 | Male | No | Thur | Lunch | 2 |
... | ... | ... | ... | ... | ... | ... | ... |
182 | 45.35 | 3.50 | Male | Yes | Sun | Dinner | 3 |
156 | 48.17 | 5.00 | Male | No | Sun | Dinner | 6 |
59 | 48.27 | 6.73 | Male | No | Sat | Dinner | 4 |
212 | 48.33 | 9.00 | Male | No | Sat | Dinner | 4 |
170 | 50.81 | 10.00 | Male | Yes | Sat | Dinner | 3 |
244 rows × 7 columns
tips.sort_values(by = "total_bill", ascending = False)
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
170 | 50.81 | 10.00 | Male | Yes | Sat | Dinner | 3 |
212 | 48.33 | 9.00 | Male | No | Sat | Dinner | 4 |
59 | 48.27 | 6.73 | Male | No | Sat | Dinner | 4 |
156 | 48.17 | 5.00 | Male | No | Sun | Dinner | 6 |
182 | 45.35 | 3.50 | Male | Yes | Sun | Dinner | 3 |
... | ... | ... | ... | ... | ... | ... | ... |
149 | 7.51 | 2.00 | Male | No | Thur | Lunch | 2 |
111 | 7.25 | 1.00 | Female | No | Sat | Dinner | 1 |
172 | 7.25 | 5.15 | Male | Yes | Sun | Dinner | 2 |
92 | 5.75 | 1.00 | Female | Yes | Fri | Dinner | 2 |
67 | 3.07 | 1.00 | Female | Yes | Sat | Dinner | 1 |
244 rows × 7 columns
Pour se limiter aux premières lignes (respectivement les dernières), on utilise la fonction head()
(resp. tail()
), qui affiche par défaut 5 lignes. Cette valeur est bien évidemment modifiable, comme vu ci-après.
tips.head(10) # 5 premières lignes par défaut
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 |
5 | 25.29 | 4.71 | Male | No | Sun | Dinner | 4 |
6 | 8.77 | 2.00 | Male | No | Sun | Dinner | 2 |
7 | 26.88 | 3.12 | Male | No | Sun | Dinner | 4 |
8 | 15.04 | 1.96 | Male | No | Sun | Dinner | 2 |
9 | 14.78 | 3.23 | Male | No | Sun | Dinner | 2 |
tips.tail(3) # 5 dernières lignes par défaut
total_bill | tip | sex | smoker | day | time | size | |
---|---|---|---|---|---|---|---|
241 | 22.67 | 2.00 | Male | Yes | Sat | Dinner | 2 |
242 | 17.82 | 1.75 | Male | No | Sat | Dinner | 2 |
243 | 18.78 | 3.00 | Female | No | Thur | Dinner | 2 |
Il y a 2 possibilités ici :
A partir de valeurs, soit vous en fournissez autant que de lignes, soit une seule qui sera donc dupliquée à toutes les lignes
tips['n_row'] = range(244)
tips['nouv'] = "nouvelle valeur"
tips.head()
total_bill | tip | sex | smoker | day | time | size | n_row | nouv | |
---|---|---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 | 0 | nouvelle valeur |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 | 1 | nouvelle valeur |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 | 2 | nouvelle valeur |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 | 3 | nouvelle valeur |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 | 4 | nouvelle valeur |
Bien évidemment, on souhaite généralement faire un calcul à partir des autres variables. Ceci peut se faire avec la fonction assign()
.
Notez l'utilisation du mot-clé lambda
pour la définition d'une fonction anonyme.
# attention ici, l.size fait référence à la taille de l, car c'est un mot clé de python
tips.assign(per_person = lambda l: round(l.total_bill / l['size'], 2))
total_bill | tip | sex | smoker | day | time | size | n_row | nouv | per_person | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 16.99 | 1.01 | Female | No | Sun | Dinner | 2 | 0 | nouvelle valeur | 8.49 |
1 | 10.34 | 1.66 | Male | No | Sun | Dinner | 3 | 1 | nouvelle valeur | 3.45 |
2 | 21.01 | 3.50 | Male | No | Sun | Dinner | 3 | 2 | nouvelle valeur | 7.00 |
3 | 23.68 | 3.31 | Male | No | Sun | Dinner | 2 | 3 | nouvelle valeur | 11.84 |
4 | 24.59 | 3.61 | Female | No | Sun | Dinner | 4 | 4 | nouvelle valeur | 6.15 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
239 | 29.03 | 5.92 | Male | No | Sat | Dinner | 3 | 239 | nouvelle valeur | 9.68 |
240 | 27.18 | 2.00 | Female | Yes | Sat | Dinner | 2 | 240 | nouvelle valeur | 13.59 |
241 | 22.67 | 2.00 | Male | Yes | Sat | Dinner | 2 | 241 | nouvelle valeur | 11.34 |
242 | 17.82 | 1.75 | Male | No | Sat | Dinner | 2 | 242 | nouvelle valeur | 8.91 |
243 | 18.78 | 3.00 | Female | No | Thur | Dinner | 2 | 243 | nouvelle valeur | 9.39 |
244 rows × 10 columns
Le calcul d'un agrégat permet de calculer une statistique de base (dénombrement, somme, moyenne, minimum, maximum - rarement autre chose) sur un tableau de données. On peut soit calculer globalement, soit pour chaque modalité d'une variable (voire chaque couple de modalités de plusieurs variables).
Pour le faire globalement, on utilise la fonction aggregate()
(ou agg()
).
tips.filter(["total_bill", "tip", "size"]).aggregate(['count', "mean"])
total_bill | tip | size | |
---|---|---|---|
count | 244.000000 | 244.000000 | 244.000000 |
mean | 19.785943 | 2.998279 | 2.569672 |
Pour le faire pour chaque modalité d'une variable, on utilise la fonction groupby()
en plus. Si on ne réalise qu'un seul calcul, on a directement les fonctions associées.
tips.filter(["sex", "total_bill", "tip", "size"]).groupby("sex").mean()
total_bill | tip | size | |
---|---|---|---|
sex | |||
Female | 18.056897 | 2.833448 | 2.459770 |
Male | 20.744076 | 3.089618 | 2.630573 |
On peut aussi définir plusieurs opérateurs d'agrégations.
tips.filter(["sex", "total_bill", "tip", "size"]).groupby("sex").aggregate(['count', "mean"])
total_bill | tip | size | ||||
---|---|---|---|---|---|---|
count | mean | count | mean | count | mean | |
sex | ||||||
Female | 87 | 18.056897 | 87 | 2.833448 | 87 | 2.459770 |
Male | 157 | 20.744076 | 157 | 3.089618 | 157 | 2.630573 |
Si on a plusieurs variables dans le regroupement, le calcul se fait donc pour chaque couple de modalités de celles-ci.
tips.filter(["sex", "smoker", "total_bill", "tip", "size"]).groupby(["sex", "smoker"]).mean()
total_bill | tip | size | ||
---|---|---|---|---|
sex | smoker | |||
Female | No | 18.105185 | 2.773519 | 2.592593 |
Yes | 17.977879 | 2.931515 | 2.242424 | |
Male | No | 19.791237 | 3.113402 | 2.711340 |
Yes | 22.284500 | 3.051167 | 2.500000 |
Le module seaborn
est basé sur matplotlib. Il faut donc ajouter la ligne %matplotlib inline dans un notebook, pour pouvoir voir les graphiques.
Ce module contient toutes les fonctions directement, l'importation est donc assez simple.
import seaborn
%matplotlib inline
La fonction displot()
nous permet de réaliser les graphiques de distribution d'une variable quantitative. Par défaut, elle réaliser un histogramme.
seaborn.displot(data = tips, x = "total_bill")
<seaborn.axisgrid.FacetGrid at 0x13f2dd460>
C'est la fonction boxplot()
qui nous permet de réaliser une boîte à moustache (soit verticale en mettant la variable en y
, soit horizontale en la mettant en x
).
seaborn.boxplot(data = tips, y = "total_bill")
<AxesSubplot:ylabel='total_bill'>
seaborn.boxplot(data = tips, x = "total_bill")
<AxesSubplot:xlabel='total_bill'>
Une autre représentation possible est obtenue avec la fonction pointplot()
, qui représente la moyenne et l'écarte-type, avec le choix entre vertical (y) ou horizontal (x).
seaborn.pointplot(data = tips, y = "total_bill")
<AxesSubplot:ylabel='total_bill'>
seaborn.pointplot(data = tips, x = "total_bill")
<AxesSubplot:xlabel='total_bill'>
Un autre graphique possible est celui obtenu avec violinplot()
, qui représente la densité d'une variable, toujours avec le choix vertical/horizontale (y/x).
seaborn.violinplot(data = tips, y = "total_bill")
<AxesSubplot:ylabel='total_bill'>
seaborn.violinplot(data = tips, x = "total_bill")
<AxesSubplot:xlabel='total_bill'>
Enfin, il est possible de représenter toutes les valeurs sur un pseudo nuage de points. Avec striplot()
dont l'option jitter
a été activée, les points sont aléatoirement répartis sur l'axe des $x$ (si on utilise y - inversement sinon).
seaborn.stripplot(data = tips, y = "total_bill", jitter = True)
<AxesSubplot:ylabel='total_bill'>
seaborn.stripplot(data = tips, x = "total_bill", jitter = True)
<AxesSubplot:xlabel='total_bill'>
Le diagramme en barres en effectifs est obtenu via la fonction countplot()
. Il est soit horizontal (avec la variable en x
), soit vertical (en y
).
seaborn.countplot(data = tips, x = "sex")
<AxesSubplot:xlabel='sex', ylabel='count'>
seaborn.countplot(data = tips, y = "sex")
<AxesSubplot:xlabel='count', ylabel='sex'>
Pour avoir la version en pourcentages (ou en proportions) de ce graphique, nous devons utiliser la fonction barplot()
, sur la table de proportions calculée avant. Cette fonction réalise un calcul (moyenne par défaut) sur une variable (ici freq
) en fonction des modalités d'une autre variable (sex
ici donc).
t = pandas.crosstab(tips.sex, "freq", normalize=True)
t = t.assign(sex = t.index, freq = 100 * t.freq)
seaborn.barplot(data = t, x = "sex", y = "freq")
<AxesSubplot:xlabel='sex', ylabel='freq'>
Pour réaliser un diagramme en barres empilées, il faudra le créer soi-même. Nous ne verrons pas ici.
Pour réaliser le nuage de points, on utilise la fonction jointplot()
. Elle a l'avantage d'ajouter par défaut les histogrammes de chaque variable. Elle réalise par défaut le nuage de points simple (scatter).
seaborn.jointplot(data = tips, x = "total_bill", y = "tip")
<seaborn.axisgrid.JointGrid at 0x141836e20>
En choississant le type reg
avec le paramètre kind
, on obtient en plus l'ajustement linéaire de la variable en y par celle en x.
seaborn.jointplot(data = tips, x = "total_bill", y = "tip", kind = "reg")
<seaborn.axisgrid.JointGrid at 0x14191b790>
Si on souhaite ne pas avoir les distributions marginales, la fonctionregplot()
nous permet de réaliser le nuage de points avec ou sans ajustement (paramètre fit_reg
).
seaborn.regplot(data = tips, x = "total_bill", y = "tip")
<AxesSubplot:xlabel='total_bill', ylabel='tip'>
seaborn.regplot(data = tips, x = "total_bill", y = "tip", fit_reg = False)
<AxesSubplot:xlabel='total_bill', ylabel='tip'>
seaborn.regplot(data = tips, x = "total_bill", y = "tip", scatter = False)
<AxesSubplot:xlabel='total_bill', ylabel='tip'>
Il est possible d'obtenir directement tous les nuages de points 2 à 2, avec la fonction pairplot()
. Le paramètre vars
permet de sélectionner certaines variables. Par défaut, la fonction utilise toutes les variables numériques.
seaborn.pairplot(data = tips, vars = ["total_bill", "tip", "size"])
<seaborn.axisgrid.PairGrid at 0x141bd0040>
Pour obtenir le diagramme en barres séparées (en effectifs), nous utilisons la fonction countplot()
avec le paramètre hue
pour indiquer la variable servant de coloriage aux barres.
seaborn.countplot(data = tips, x = "sex", hue = "smoker")
<AxesSubplot:xlabel='sex', ylabel='count'>
Pour réaliser les boîtes à moustaches de la variable quantitative pour chaque modalité de la variable qualitative, on utilise la fonction catplot()
.
seaborn.catplot(data = tips, x = "sex", y = "total_bill", kind = "box")
<seaborn.axisgrid.FacetGrid at 0x141b22a60>
On peut aussi représenter la moyenne et l'écart-type à l'aide du graphique pointplot
(qu'on réalise ici via catplot()
).
seaborn.catplot(data = tips, x = "sex", y = "total_bill", kind = "point", join = False)
<seaborn.axisgrid.FacetGrid at 0x142073610>
Il est bien évidemment possible de personnaliser le graphique de différentes façons, dont certains sont présentées ci-dessous. On accède aux fonctions de personnalisation soit via des paramètres de la fonction, soit via l'objet renvoyé par la fonction utilisée pour créer le graphique, soit via le module directement. Dans ce cas, ce sont des changements qui affecteront aussi les graphiques futurs.
suptitle
dans fig
: titre globalset_axis_labels
: titre des axespalette
: choix d'une palette de couleurssize
et aspect
: hauteur et ratio entre hauteur et largeur, pour chaque facette (une seule ici)seaborn.set_style("white")
p = seaborn.catplot(data = tips, x = "size", y = "tip", hue = "sex",
kind = "box",
palette = "Set2", height = 4, aspect = 2)
p.fig.suptitle("Taille et pourboire en fonction du sexe")
p.set_axis_labels("Nombre de convives", "Pourboire")
<seaborn.axisgrid.FacetGrid at 0x142168340>
Nous allons travailler sur des données Velib en temps réel, qui sont disponibles sur cette page.
Pour cela, nous avons besoin du module requests
, tel qu'utilisé ci-dessous.
import requests
url = "https://opendata.paris.fr/api/records/1.0/search/"
par = dict(
dataset = "velib-disponibilite-en-temps-reel"
)
r = requests.get(url = url, params = par)
r.json()
{'nhits': 1396, 'parameters': {'dataset': 'velib-disponibilite-en-temps-reel', 'timezone': 'UTC', 'rows': 10, 'start': 0, 'format': 'json'}, 'records': [{'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': 'a29002eb405dc62e777941496253e5b284ad7e1e', 'fields': {'ebike': 1, 'capacity': 35, 'name': 'Benjamin Godard - Victor Hugo', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 2, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 1, 'stationcode': '16107', 'coordonnees_geo': [48.865983, 2.275725], 'numdocksavailable': 33, 'duedate': '2021-03-29T14:45:32+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.275725, 48.865983]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': 'd5035012dac48e8f0ac947b3f24ed002675e72b3', 'fields': {'ebike': 5, 'capacity': 55, 'name': 'André Mazet - Saint-André des Arts', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 15, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 10, 'stationcode': '6015', 'coordonnees_geo': [48.8537558106, 2.33909580857], 'numdocksavailable': 39, 'duedate': '2021-03-29T14:46:00+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.33909580857, 48.8537558106]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '0b24b8517c08613e7d3f3bdeb2595995bfc82d1f', 'fields': {'ebike': 1, 'capacity': 20, 'name': 'Charonne - Robert et Sonia Delauney', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 1, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 0, 'stationcode': '11104', 'coordonnees_geo': [48.855907556, 2.39257067442], 'numdocksavailable': 18, 'duedate': '2021-03-29T14:44:38+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.39257067442, 48.855907556]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': 'c0648d5ba64e622de5b98851e7ee6db990fb5dab', 'fields': {'ebike': 2, 'capacity': 21, 'name': 'Toudouze - Clauzel', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 3, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 1, 'stationcode': '9020', 'coordonnees_geo': [48.8792959173, 2.33736008406], 'numdocksavailable': 18, 'duedate': '2021-03-29T14:49:32+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.33736008406, 48.8792959173]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': 'fe6d908a6eda0f31a2cf9c6e453e0d6c4593b493', 'fields': {'ebike': 6, 'capacity': 46, 'name': 'Harpe - Saint-Germain', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 22, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 16, 'stationcode': '5001', 'coordonnees_geo': [48.851518815, 2.34367031604], 'numdocksavailable': 21, 'duedate': '2021-03-29T14:48:47+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.34367031604, 48.851518815]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '9ed5e466d7ac983305c2204542317239b66399b4', 'fields': {'ebike': 2, 'capacity': 60, 'name': 'Jourdan - Stade Charléty', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 2, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 0, 'stationcode': '14014', 'coordonnees_geo': [48.8194283334, 2.34333537519], 'numdocksavailable': 56, 'duedate': '2021-03-29T14:47:54+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.34333537519, 48.8194283334]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': 'e79ff17c92180b144eeee2d7ba2f816fe9051006', 'fields': {'ebike': 3, 'capacity': 39, 'name': 'Guersant - Gouvion-Saint-Cyr', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 14, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 11, 'stationcode': '17041', 'coordonnees_geo': [48.8828777518, 2.28766737081], 'numdocksavailable': 24, 'duedate': '2021-03-29T14:49:30+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.28766737081, 48.8828777518]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '4621af9fb9f38476a54cf92b4a51b7596d18a152', 'fields': {'ebike': 3, 'capacity': 60, 'name': 'Alibert - Jemmapes', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 9, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 6, 'stationcode': '10013', 'coordonnees_geo': [48.871044052, 2.36610446199], 'numdocksavailable': 47, 'duedate': '2021-03-29T14:48:57+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.36610446199, 48.871044052]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '5615459ca31e5106def4e6f3cbdc5b00bdbbf410', 'fields': {'ebike': 0, 'capacity': 22, 'name': 'Caumartin - Provence', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 17, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 17, 'stationcode': '9104', 'coordonnees_geo': [48.8744227734, 2.32846856117], 'numdocksavailable': 5, 'duedate': '2021-03-29T14:44:59+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.32846856117, 48.8744227734]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '6bc823aa097885137eaccf9bdda129cb749730fd', 'fields': {'ebike': 2, 'capacity': 25, 'name': 'Cassini - Denfert-Rochereau', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 5, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 3, 'stationcode': '14111', 'coordonnees_geo': [48.8375258391, 2.33603540808], 'numdocksavailable': 19, 'duedate': '2021-03-29T14:48:19+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.33603540808, 48.8375258391]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}]}
Dans ce résultat, nous voyons qu'il y a 1393 enregistrements à récupérer (nhits
), et que nous en avons récupérer ici uniquement 10 (parameters.rows
). Pour avoir les 10 suivants, on doit faire comme ci-dessous
par = dict(
dataset = "velib-disponibilite-en-temps-reel",
start = 11
)
rsuite = requests.get(url = url, params = par)
rsuite.json()
{'nhits': 1396, 'parameters': {'dataset': 'velib-disponibilite-en-temps-reel', 'timezone': 'UTC', 'rows': 10, 'start': 11, 'format': 'json'}, 'records': [{'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '2e5c7ec9c823ab04899b3b136fcc76511409e51f', 'fields': {'ebike': 0, 'capacity': 17, 'name': 'Saint-Romain - Cherche-Midi', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 12, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 12, 'stationcode': '6108', 'coordonnees_geo': [48.8470815908, 2.32137478888], 'numdocksavailable': 5, 'duedate': '2021-03-29T14:48:13+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.32137478888, 48.8470815908]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': 'd07bc708c41edfc07870aad3a781368f4ab81530', 'fields': {'ebike': 0, 'capacity': 31, 'name': 'André Karman - République', 'nom_arrondissement_communes': 'Aubervilliers', 'numbikesavailable': 2, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 2, 'stationcode': '33006', 'coordonnees_geo': [48.9103987576, 2.38513559103], 'numdocksavailable': 30, 'duedate': '2021-03-29T14:45:14+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.38513559103, 48.9103987576]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '8dd245b0601d1ef47c4b54293bfe114b0c2137b3', 'fields': {'ebike': 2, 'capacity': 27, 'name': 'Pierre et Marie Curie - Maurice Thorez', 'nom_arrondissement_communes': 'Ivry-sur-Seine', 'numbikesavailable': 3, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 1, 'stationcode': '42016', 'coordonnees_geo': [48.8158022636, 2.37680498511], 'numdocksavailable': 24, 'duedate': '2021-03-29T14:44:29+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.37680498511, 48.8158022636]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '8bf875b5b29b194cb4793e88967543353f8202bc', 'fields': {'ebike': 9, 'capacity': 50, 'name': 'Bois de Vincennes - Gare', 'nom_arrondissement_communes': 'Nogent-sur-Marne', 'numbikesavailable': 20, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 11, 'stationcode': '41301', 'coordonnees_geo': [48.8360222429, 2.47083399508], 'numdocksavailable': 29, 'duedate': '2021-03-29T14:49:05+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.47083399508, 48.8360222429]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '54b03c450350a1589920e2ea1b949299de2c4d68', 'fields': {'ebike': 5, 'capacity': 25, 'name': 'Silly - Galliéni', 'nom_arrondissement_communes': 'Boulogne-Billancourt', 'numbikesavailable': 10, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 5, 'stationcode': '21010', 'coordonnees_geo': [48.8355838387, 2.23255008459], 'numdocksavailable': 14, 'duedate': '2021-03-29T14:48:53+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.23255008459, 48.8355838387]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '5190c7ce1be0e26d551b49d4da36a76fbd7d15e7', 'fields': {'ebike': 1, 'capacity': 20, 'name': 'Beaux-Arts - Bonaparte', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 18, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 17, 'stationcode': '6021', 'coordonnees_geo': [48.8564519854, 2.33485188335], 'numdocksavailable': 3, 'duedate': '2021-03-29T14:46:21+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.33485188335, 48.8564519854]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '2afd6b6f6fb4f19e4c23395aa437427f0e3b2b00', 'fields': {'ebike': 0, 'capacity': 22, 'name': 'Place Nelson Mandela', 'nom_arrondissement_communes': 'Rueil-Malmaison', 'numbikesavailable': 0, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 0, 'stationcode': '25006', 'coordonnees_geo': [48.8620909377, 2.19657629728], 'numdocksavailable': 21, 'duedate': '2021-03-29T14:45:39+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.19657629728, 48.8620909377]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '1ce7c615124daf390bb960e3a312d89fd86b8074', 'fields': {'ebike': 11, 'capacity': 62, 'name': 'Grande Armée - Brunel', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 15, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 4, 'stationcode': '17038', 'coordonnees_geo': [48.876116, 2.288124], 'numdocksavailable': 45, 'duedate': '2021-03-29T14:46:21+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.288124, 48.876116]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '0828d8fd2cc74abff5c1fce11576d572974f250e', 'fields': {'ebike': 6, 'capacity': 29, 'name': 'Invalides - Duroc', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 25, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 19, 'stationcode': '7001', 'coordonnees_geo': [48.8477924733, 2.31619209051], 'numdocksavailable': 4, 'duedate': '2021-03-29T14:45:26+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.31619209051, 48.8477924733]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}, {'datasetid': 'velib-disponibilite-en-temps-reel', 'recordid': '0d8a63ea36a88cf2a9758d34366d69921699d11a', 'fields': {'ebike': 0, 'capacity': 52, 'name': 'Morillons - Dantzig', 'nom_arrondissement_communes': 'Paris', 'numbikesavailable': 1, 'is_installed': 'OUI', 'is_renting': 'OUI', 'mechanical': 1, 'stationcode': '15047', 'coordonnees_geo': [48.8331014995, 2.29938000441], 'numdocksavailable': 50, 'duedate': '2021-03-29T14:43:50+00:00', 'is_returning': 'OUI'}, 'geometry': {'type': 'Point', 'coordinates': [2.29938000441, 48.8331014995]}, 'record_timestamp': '2021-03-29T15:00:06.473000+00:00'}]}
Ce qui nous intéresse ici est le champ records
, qui contient les enregistrements de 10 premières stations donc. En le transformant en data frame avec pandas
, on voit que le tableau obtenu n'est pas très lisible.
records = r.json()['records']
df = pandas.DataFrame(records)
df
datasetid | recordid | fields | geometry | record_timestamp | |
---|---|---|---|---|---|
0 | velib-disponibilite-en-temps-reel | a29002eb405dc62e777941496253e5b284ad7e1e | {'ebike': 1, 'capacity': 35, 'name': 'Benjamin... | {'type': 'Point', 'coordinates': [2.275725, 48... | 2021-03-29T15:00:06.473000+00:00 |
1 | velib-disponibilite-en-temps-reel | d5035012dac48e8f0ac947b3f24ed002675e72b3 | {'ebike': 5, 'capacity': 55, 'name': 'André Ma... | {'type': 'Point', 'coordinates': [2.3390958085... | 2021-03-29T15:00:06.473000+00:00 |
2 | velib-disponibilite-en-temps-reel | 0b24b8517c08613e7d3f3bdeb2595995bfc82d1f | {'ebike': 1, 'capacity': 20, 'name': 'Charonne... | {'type': 'Point', 'coordinates': [2.3925706744... | 2021-03-29T15:00:06.473000+00:00 |
3 | velib-disponibilite-en-temps-reel | c0648d5ba64e622de5b98851e7ee6db990fb5dab | {'ebike': 2, 'capacity': 21, 'name': 'Toudouze... | {'type': 'Point', 'coordinates': [2.3373600840... | 2021-03-29T15:00:06.473000+00:00 |
4 | velib-disponibilite-en-temps-reel | fe6d908a6eda0f31a2cf9c6e453e0d6c4593b493 | {'ebike': 6, 'capacity': 46, 'name': 'Harpe - ... | {'type': 'Point', 'coordinates': [2.3436703160... | 2021-03-29T15:00:06.473000+00:00 |
5 | velib-disponibilite-en-temps-reel | 9ed5e466d7ac983305c2204542317239b66399b4 | {'ebike': 2, 'capacity': 60, 'name': 'Jourdan ... | {'type': 'Point', 'coordinates': [2.3433353751... | 2021-03-29T15:00:06.473000+00:00 |
6 | velib-disponibilite-en-temps-reel | e79ff17c92180b144eeee2d7ba2f816fe9051006 | {'ebike': 3, 'capacity': 39, 'name': 'Guersant... | {'type': 'Point', 'coordinates': [2.2876673708... | 2021-03-29T15:00:06.473000+00:00 |
7 | velib-disponibilite-en-temps-reel | 4621af9fb9f38476a54cf92b4a51b7596d18a152 | {'ebike': 3, 'capacity': 60, 'name': 'Alibert ... | {'type': 'Point', 'coordinates': [2.3661044619... | 2021-03-29T15:00:06.473000+00:00 |
8 | velib-disponibilite-en-temps-reel | 5615459ca31e5106def4e6f3cbdc5b00bdbbf410 | {'ebike': 0, 'capacity': 22, 'name': 'Caumarti... | {'type': 'Point', 'coordinates': [2.3284685611... | 2021-03-29T15:00:06.473000+00:00 |
9 | velib-disponibilite-en-temps-reel | 6bc823aa097885137eaccf9bdda129cb749730fd | {'ebike': 2, 'capacity': 25, 'name': 'Cassini ... | {'type': 'Point', 'coordinates': [2.3360354080... | 2021-03-29T15:00:06.473000+00:00 |
On souhaite finalement se restreindre qu'aux informations contenues dans le champs fields
. Pour cela, on utilise le mécanisme de list comprehension.
pandas.DataFrame([s['fields'] for s in records])
ebike | capacity | name | nom_arrondissement_communes | numbikesavailable | is_installed | is_renting | mechanical | stationcode | coordonnees_geo | numdocksavailable | duedate | is_returning | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 35 | Benjamin Godard - Victor Hugo | Paris | 2 | OUI | OUI | 1 | 16107 | [48.865983, 2.275725] | 33 | 2021-03-29T14:45:32+00:00 | OUI |
1 | 5 | 55 | André Mazet - Saint-André des Arts | Paris | 15 | OUI | OUI | 10 | 6015 | [48.8537558106, 2.33909580857] | 39 | 2021-03-29T14:46:00+00:00 | OUI |
2 | 1 | 20 | Charonne - Robert et Sonia Delauney | Paris | 1 | OUI | OUI | 0 | 11104 | [48.855907556, 2.39257067442] | 18 | 2021-03-29T14:44:38+00:00 | OUI |
3 | 2 | 21 | Toudouze - Clauzel | Paris | 3 | OUI | OUI | 1 | 9020 | [48.8792959173, 2.33736008406] | 18 | 2021-03-29T14:49:32+00:00 | OUI |
4 | 6 | 46 | Harpe - Saint-Germain | Paris | 22 | OUI | OUI | 16 | 5001 | [48.851518815, 2.34367031604] | 21 | 2021-03-29T14:48:47+00:00 | OUI |
5 | 2 | 60 | Jourdan - Stade Charléty | Paris | 2 | OUI | OUI | 0 | 14014 | [48.8194283334, 2.34333537519] | 56 | 2021-03-29T14:47:54+00:00 | OUI |
6 | 3 | 39 | Guersant - Gouvion-Saint-Cyr | Paris | 14 | OUI | OUI | 11 | 17041 | [48.8828777518, 2.28766737081] | 24 | 2021-03-29T14:49:30+00:00 | OUI |
7 | 3 | 60 | Alibert - Jemmapes | Paris | 9 | OUI | OUI | 6 | 10013 | [48.871044052, 2.36610446199] | 47 | 2021-03-29T14:48:57+00:00 | OUI |
8 | 0 | 22 | Caumartin - Provence | Paris | 17 | OUI | OUI | 17 | 9104 | [48.8744227734, 2.32846856117] | 5 | 2021-03-29T14:44:59+00:00 | OUI |
9 | 2 | 25 | Cassini - Denfert-Rochereau | Paris | 5 | OUI | OUI | 3 | 14111 | [48.8375258391, 2.33603540808] | 19 | 2021-03-29T14:48:19+00:00 | OUI |
leaflet
¶Dans ce TP, nous allons aussi aborder l'aspect cartographie sous python
avec le package folium
(à installer donc). L'idée sera d'ajouter à un fond de carte des formes de couleurs, en fonction d'une information tierce. Le package folium
est une interface entre python
et la librairie leaflet
. Elle s'utilise très facilement, comme nous pouvons le voir ci-dessous pour la création d'une carte. Les coordonnées indiquées sont la latitude et la longitude de Paris.
import folium
centre = [48.87, 2.35]
paris = folium.Map(location = centre, zoom_start = 12)
paris
En reprenant le data frame vu juste avant (et en le stockant dans une variable), on peut créer une carte avec des marqueurs, qui affichent le nom de la station lorsqu'on passe la souris dessus, et le nom de la station + la ville en cliquant dessus (notez l'usage de code HTML possible dans la pop-up).
def popup(n, v):
return '<strong>'+n+'</strong> ('+v+')'
df = pandas.DataFrame([s['fields'] for s in records])
paris = folium.Map(location = centre, zoom_start = 12)
for station in [s['fields'] for s in records]:
folium.Marker(station["coordonnees_geo"],
tooltip = station["name"],
popup = popup(station["name"], station["nom_arrondissement_communes"])).add_to(paris)
paris
pandas
capacity
) des stationsnom_arrondissement_communes
), calculer :capacity
)numbikesavailable
)numdocksavailable
)