import pandas as pdon a fait un sondage, on a demandé à des gens ce qu’ils pensaient devoir être amélioré dans leur environnement
les réponses se présentent comme cela, chaque ligne correspond à une réponse
df = pd.read_csv(
'data/split-count-clean.csv', sep=';')
dfon veut faire une synthèse, et pour cela on voudrait transformer ça pour en faire ceci
| city | bins | flowers | toilets | home | answers |
|---|---|---|---|---|---|
| aberdeen | 1 | 3 | 2 | 1 | 3 |
| ... | |||||
| london | 1 | 2 | 1 | 0 | 2 |
par exemple pour pouvoir les dessiner simplement
remarquez la colonne answers qui décompte le nombre total de réponses dans cette ville
on suppose pour commencer que toutes les réponses sont “propres” c’est-à-dire que toutes les réponses qui parlent de fleurs sont orthographiées de la même façon
groupement¶
il faut donc grouper les lignes par ville, et faire une sorte de somme sur les sous-groupes
idée #1¶
groups = df.groupby(by='city')groups.aggregate("sum")mais ça c’est pas terrible parce qu’on a des chaines et qu’on va avoir du mal à compter; et aussi vous remarquez le flowersbathroom parce qu’on a additionné les chaines brutalement..
donc ce serait mieux de penser en termes de liste
idée #2¶
avant de faire la somme, on éclate les chaines en utilisant le séparateur ‘,’
comment on fait ça ? c’est le propos de la méthode str.split()
et rappelez vous, .str est utile lorsqu’on veut appliquer une méthode de chaine sur une df
df2 = df.copy()
df2['to-improve'] = (
df2['to-improve']
.str.replace(' ','') # ça ne fait pas de mal de nettoyer
.str.split(',')
)
df2# maintenant je peux faire la somme entre ces listes
totals = df2.groupby('city').aggregate(sum)
totalsça progresse...
faire le compte¶
pour faire le compte sur chaque liste, il y a en Python de base une classe Counter
https://
from collections import Counterc’est peut-être un peu inhabituel pour les gens qui ne pratiquent pas Python, mais
comme c’est une classe, on peut l’appeler pour construire un objet - de type Counter donc
# un exemple d'appel de Counter sur une liste
Counter(['john', 'mary', 'mary', 'john', 'john', 'john', 'mary'])Counter({'john': 4, 'mary': 3})# je peux donc utiliser apply sur la série 'to-improve'
# pour calculer une nouvelle série dont les éléments sont des objets 'Counter'
totals['to-improve'].apply(Counter)city
aberdeen {'bins': 1, 'flowers': 3, 'toilets': 2, 'home'...
london {'flowers': 2, 'toilets': 1, 'bins': 1}
oxford {'bins': 3, 'flowers': 2, 'toilets': 1, 'home'...
Name: to-improve, dtype: object# et pour l'insérer dans la dataframe, à la place de la précédente
totals['to-improve'] = totals['to-improve'].apply(Counter)
totalsdigression¶
# il faut savoir qu'un objet Counter est aussi un dictionnaire
c = Counter(['john', 'mary', 'mary', 'john', 'john', 'john', 'mary'])
isinstance(c, dict)True# on peut donc créer une Series à partir d'un Counter
pd.Series(c)john 4
mary 3
dtype: int64éclater¶
Maintenant ce qu’on va faire c’est en gros éclater chaque cellule de droite en ... une nouvelle Series
et là c’est un peu magique il faut bien avouer:
d’abord la création de la
Seriesà partir deCounter; qui fait exactement ce qu’on veut, les clés dans l’objetCounterservent à remplir l’index de laSeriesensuite en remplaçant chaque valeur dans la Series par une nouvelle
Series, on crée .. une dataframe
# pour bien voir le point #1:
pd.Series(Counter([True, False, False, True, True, True, False]))True 4
False 3
dtype: int64# et maintenant grâce au point 2, on obtient .. ce qu'on voulait
improvements = totals['to-improve'].apply(pd.Series)
improvementsenfin presque¶
à ce stade, il nous reste à faire:
compter le nombre de réponses (la colonne
answers)remplacer les n/a par zéro
enfin si on regarde attentivement, il y a une colonne en trop, avec un nom vide
c’est lié à la ligne #4 dans la df de départ, la chaine se termine par une ,
du coup quand on fait le split() ça nous ajoute une chaine vide, parce que:
# remarquez la chaine vide à la fin du résultat
"a,b,c,".split(',')['a', 'b', 'c', '']le nombre de réponses¶
answers = df.groupby(by='city').aggregate('count')
complete = improvements.join(answers)
complete# en option on peut renommer la colonne 'to-improve'
complete = complete.rename(columns={'to-improve': 'answers'})
completefillna¶
# on en profite pour remettre des entiers ...
complete.fillna(value=0, inplace=True, downcast='infer')
completela colonne ‘chaine vide’¶
on aurait pu traiter le problème à un stade plus précoce, mais à ce stade-ci on peut toujours simplement enlever la colonne
complete.columnsIndex(['bins', 'flowers', 'toilets', 'home', '', 'answers'], dtype='object')# à n'exécuter qu'une seule fois
# del complete['']
# du coup juste pour être tolérant par rapport à un éventuel double-run
if '' in complete:
del complete['']
completedessiner¶
# ça nécessite un `pip install ipympl`
%matplotlib ipymplimport matplotlib.pyplot as plt# pas forcément très intéressant, car df.plot() dessine les colonnes
# complete.plot();# du coup c'est plus pertinent de le faire sur la transposée
complete.T.plot();v2¶
pour les rapides, à titre d’exercice:
en vrai les données ne sont pas propres, les gens ont utilisé des synonymes
df = pd.read_csv('data/split-count.csv', sep=';')
dfpour essayer de gérer la diversité on se définit - à la main - un tableau de synonymes
synonyms = {
'bins': ['bin', 'trash', 'dump'],
'toilets': ['toilet', 'bathroom', 'restroom'],
}et on va utiliser ça pour dire que, par exemple tous les mots qui contiennent “bathroom” ou “restroom” ou “toilet” seront comptabilisés dans la colonne “toilets”
# à vous de jouer...