Tutoriel d’introduction à Pandas

Analyse de données en Python – notions avancées

Dans cette chronique, nous allons faire suite à l'article précédent sur Pandas et nous examinerons plus en détail la manière d'utiliser cette bibliothèque pour une analyse plus approfondie des données. Nous chercherons à identifier les doublons et à ranger nos données pour créer des informations utiles ultérieurement.

1 commentaire Donner une note  l'article (5)

Article lu   fois.

Les deux auteur et traducteur

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Accéder aux colonnes

Nous avons vu dans l'article précédent que nous pouvons accéder aux colonnes du DataFrame grâce aux variables loc et iloc, en fournissant soit un index, soit le nom de la colonne.

Mais il existe également un autre moyen : vous pouvez utiliser l’opérateur point (.) sur le DataFrame et appeler directement le nom de la colonne. Cela se traduira par un ensemble d’éléménts de la colonne sélectionnée :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
>>> baby_names.BRTH_YR.head()
0    2011
1    2011
2    2011
3    2011
4    2011
Name: BRTH_YR, dtype: int64

Il est parfaitement acceptable de s'en tenir à n'importe quelle approche. Parfois, j'utilise l'accès direct, d'autres fois, je tranche.

II. Typos et autres erreurs de saisie

C'est un sujet difficile à résoudre, car les fautes de frappe sont de petites erreurs que vous ne remarquez pas et il est difficile de trouver une solution automatisée pour les corriger. Cependant, vous pouvez parfois voir et identifier des problèmes dans le jeu de données qui sont faciles à résoudre, comme une dénomination différente de la même valeur. Si vous avez consulté le fichier baby_names.csv, vous avez probablement remarqué que l’appartenance ethnique se présente sous différentes formes :

 
Sélectionnez
1.
2.
3.
4.
5.
>>> import numpy as np
>>> np.unique(baby_names.iloc[:, 2])
array(['ASIAN AND PACI', 'ASIAN AND PACIFIC ISLANDER', 'BLACK NON HISP',
      'BLACK NON HISPANIC', 'HISPANIC', 'WHITE NON HISP',
      'WHITE NON HISPANIC'], dtype=object)

Des valeurs plus longues ont été coupées dans certains cas. Pour éviter les ambiguïtés plus tard, nous amènerons chaque entrée dans sa forme la plus longue :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
>>> baby_names.loc[baby_names.ETHCTY == 'ASIAN AND PACI', 'ETHCTY'] = 'ASIAN AND PACIFIC ISLANDER'
>>> baby_names.loc[baby_names.ETHCTY == 'BLACK NON HISP', 'ETHCTY'] = 'BLACK NON HISPANIC'
>>> baby_names.loc[baby_names.ETHCTY == 'WHITE NON HISP', 'ETHCTY'] = 'WHITE NON HISPANIC'
>>> np.unique(baby_names.iloc[:, 2])
array(['ASIAN AND PACIFIC ISLANDER', 'BLACK NON HISPANIC', 'HISPANIC',
      'WHITE NON HISPANIC'], dtype=object)

Pour ce faire, nous créons un sélecteur basé sur l'appartenance ethnique de chacune des trois valeurs qui se présentent sous deux variantes, nous l' appliquons à l' emplacement du DataFrame, puis nous définissons la nouvelle valeur. Cela remplace vraiment les anciennes valeurs par les nouvelles. À la fin, nous vérifions que les valeurs sont réellement mises à jour et que les valeurs abrégées ont disparu de l'ensemble de données.

Un autre problème peut être que nous avons des noms dont toutes les lettres sont majuscules. Nous pouvons utiliser les connaissances de l'exemple ci-dessus pour améliorer davantage l'ensemble de données :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
>>> baby_names.NM.head()
0         GERALDINE
1               GIA
2            GIANNA
3           GISELLE
4             GRACE
>>> baby_names.NM = baby_names.NM.str.capitalize()
>>> baby_names.NM.head()
0    Geraldine
1          Gia
2       Gianna
3      Giselle
4        Grace
Name: NM, dtype: object

Vous pouvez voir que nous avons écrasé les valeurs dans les colonnes avec leur représentation en majuscules. Cela améliore l'affichage des données et facilite l’élimination des doublons éventuels.

III. Identification des données en double et effacement du jeu de données

Ce cas de figure est vraiment utile. Chaque fois que vous obtenez un nouvel ensemble de données, vous devez faire attention aux doublons et, si vous en trouvez, demander à votre client ce que vous devez faire avec les informations dupliquées. La plupart du temps, la réponse sera de mettre en ordre l'ensemble de données, puis de réexécuter votre analyse.

Les doublons peuvent être identifiés avec la méthode duplicated() de la classe DataFrame. Cette méthode retourne une série de valeurs True et False que vous pouvez utiliser pour afficher les lignes dupliquées :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
 >>> baby_names.duplicated().head()
0    False
1    False
2    False
3    False
4    False
dtype: bool
>>> baby_names[baby_names.duplicated()].head()
     BRTH_YR    GNDR              ETHCTY        NM  CNT  RNK
1963     2011  FEMALE  WHITE NON HISPANIC  Scarlett   31   60
1964     2011  FEMALE  WHITE NON HISPANIC    Serena   16   75
1965     2011  FEMALE  WHITE NON HISPANIC    Shaina   16   75
1966     2011  FEMALE  WHITE NON HISPANIC  Shaindel   15   76
1967     2011  FEMALE  WHITE NON HISPANIC   Shaindy   48   44

Vous pouvez voir que lorsque nous transmettons la série contenant les doublons identifiés relativement au DataFrame d'origine en tant qu'argument de tri, nous récupérons toutes les lignes (un sous-ensemble du DataFrame) dupliquées. L'extraction ne montre rien d'intéressant, il semble que les valeurs ne soient pas des doublons. Mais si nous trions les résultats, nous verrons qu’il y a un problème :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
 >>> baby_names[baby_names.duplicated()].sort_values(by='NM').head()
     BRTH_YR    GNDR              ETHCTY       NM  CNT  RNK
2582     2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5
4082     2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5
2766     2011  FEMALE            HISPANIC  Aaliyah   63   30
4266     2011  FEMALE            HISPANIC  Aaliyah   63   30
6045     2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5

Nous pouvons voir qu'il y a des lignes dupliquées dans l'ensemble de données. Si nous approfondissons un peu, nous pouvons constater que tous les doublons se trouvent dans le jeu de données de 2011 :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
 >>> baby_names[baby_names.duplicated()].describe()
      BRTH_YR          CNT          RNK
count   5889.0  5889.000000  5889.000000
mean    2011.0    34.602140    56.494142
std        0.0    40.513511    25.012470
min     2011.0    10.000000     1.000000
25%     2011.0    13.000000    37.000000
50%     2011.0    20.000000    60.000000
75%     2011.0    36.000000    77.000000
max     2011.0   426.000000    97.000000

Il est maintenant temps de ranger notre liste. Heureusement, Pandas a pensé à cela aussi et DataFrame a une méthode appelée drop_duplicates () qui, comme son nom le suggère déjà, supprime les doublons et renvoie le résultat nettoyé. Vous pouvez indiquer certains arguments à la méthode pour ajuster le rangement, par exemple pour ranger les données sur place, mais il est recommandé de conserver le jeu de données d'origine si vous testez vos données, car vous avez parfois besoin de la version d'origine. Il ne sera pas facile de retrouver cette version originelle si vous y avez apporté beaucoup de transformations.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
 >>> clean_baby_names = baby_names.drop_duplicates()
>>> clean_baby_names.describe()
          BRTH_YR          CNT          RNK
count  8073.000000  8073.000000  8073.000000
mean   2012.522482    34.480243    57.272761
std       1.118043    39.931078    25.609985
min    2011.000000    10.000000     1.000000
25%    2012.000000    13.000000    38.000000
50%    2013.000000    20.000000    59.000000
75%    2014.000000    36.000000    78.000000
max    2014.000000   426.000000   102.000000
>>> clean_baby_names[clean_baby_names.NM == 'Aaliyah']
      BRTH_YR    GNDR              ETHCTY       NM  CNT  RNK
1110      2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5
1294      2011  FEMALE            HISPANIC  Aaliyah   63   30
8031      2012  FEMALE  BLACK NON HISPANIC  Aaliyah   55   10
8202      2012  FEMALE            HISPANIC  Aaliyah   68   26
10031     2013  FEMALE  BLACK NON HISPANIC  Aaliyah   73    3
10212     2013  FEMALE            HISPANIC  Aaliyah   56   33
12072     2014  FEMALE  BLACK NON HISPANIC  Aaliyah   67    4
12259     2014  FEMALE            HISPANIC  Aaliyah   55   36

J'ai assigné les données nettoyées à une nouvelle variable. Nous voyons maintenant que les données semblent être propres, car la moyenne de la colonne BRTH_YR se situe entre 2012 et 2013, ce qui correspond au milieu de notre ensemble de données.

La méthode drop_duplicates () définit les lignes en double si toutes leurs colonnes ont la même valeur. Par conséquent, nous pouvons être assurés que nous n'avons perdu aucune information au cours de notre processus.

Nous pouvons aussi sélectionner des colonnes pour identifier les doublons. Par exemple, imaginons qu'un nom soit entré deux fois pour la même année et la même ethnie, mais avec des comptes et des rangs différents. Dans ce cas, ces lignes resteraient dans le jeu de données, mais nous ne le voulons pas, car cela modifie notre analyse. Pour identifier de telles entrées, nous pouvons affiner notre requête en la personnalisant :

 
Sélectionnez
1.
2.
3.
4.
 >>> clean_baby_names.duplicated().sum()
0
>>> clean_baby_names.duplicated(['NM', 'BRTH_YR','ETHCTY']).sum()
65

Il semble que nous ayons trouvé des données dupliquées. Vérifions qu'il s'agit bien de doublons avant de les supprimer:

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
 >>> clean_baby_names[clean_baby_names.duplicated(['NM', 'BRTH_YR','ETHCTY'])].sort_values('NM').head()
      BRTH_YR    GNDR              ETHCTY      NM  CNT  RNK
13344     2014    MALE            HISPANIC  Alexis   40   68
1132      2011  FEMALE  BLACK NON HISPANIC   Angel   16   39
9551      2012    MALE  WHITE NON HISPANIC   Ariel   20   84
9268      2012    MALE            HISPANIC   Ariel   18   92
1340      2011  FEMALE            HISPANIC   Ariel   17   71
>>> clean_baby_names[(clean_baby_names.NM == 'Alexis') & (clean_baby_names.ETHCTY == 'HISPANIC') & (clean_baby_names.BRTH_YR == 2014)]
      BRTH_YR    GNDR    ETHCTY      NM  CNT  RNK
12272     2014  FEMALE  HISPANIC  Alexis   13   70
13344     2014    MALE  HISPANIC  Alexis   40   68

Après avoir créé le filtrage, nous n’obtenons qu’un résultat pour le nom Alexis. C'est suspect, alors nous avons toutes les entrées pour Alexis, pour l'année 2014 ayant l'ethnie hispanique. Nous voyons maintenant qu’il y a deux résultats : un pour les femmes et un pour les hommes. Donc, ce ne sont pas de vrais doublons. Nous pourrions poursuivre la recherche des différents noms de notre liste pour vérifier qu'ils sont bien divisés entre MALE et FEMALE, mais nous pouvons ajouter la colonne de genre à notre filtre de doublon :

 
Sélectionnez
1.
2.
3.
 >>> clean_baby_names.duplicated(['NM', 'BRTH_YR','ETHCTY','GNDR']).sum()
0
_YR','ETHCTY','GNDR']).sum() 0

OK, plus de doublon. Naturellement, si vous avez une bonne connaissance du domaine, vous pouvez identifier des noms qui ne peuvent être que masculins ou féminins et les filtrer ou les agréger.

IV. Traitement NaN et N/A

Parfois, vous devez gérer des jeux de données dans lesquels certains champs indispensables pour vos calculs ne sont pas renseignés, par exemple avec le Machine Learning, mais nous n'avons pas à aller aussi loin, car les fonctions d'agrégation ont également besoin de valeurs. Pour résoudre ce problème, Pandas dispose d’une méthode : fillna.

Par exemple, modifions certaines des valeurs de notre DataFrame baby_names pour générer des valeurs manquantes :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
>>> import numpy as np
>>> na_baby_names = clean_baby_names.copy()
>>> na_baby_names.loc[na_baby_names.NM == 'Riley', 'CNT'] = np.nan
>>> na_baby_names.loc[na_baby_names.NM == 'Avery', 'RNK'] = ''
>>> na_baby_names.describe()
          BRTH_YR          CNT
count  8073.000000  8049.000000
mean   2012.522482    34.527519
std       1.118043    39.978346
min    2011.000000    10.000000
25%    2012.000000    13.000000
50%    2013.000000    20.000000
75%    2014.000000    36.000000
max    2014.000000   426.000000
>>> na_baby_names.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 8073 entries, 0 to 13961
Data columns (total 6 columns):
BRTH_YR    8073 non-null int64
GNDR       8073 non-null object
ETHCTY     8073 non-null object
NM         8073 non-null object
CNT        8049 non-null float64
RNK        8073 non-null object
dtypes: float64(1), int64(1), object(4)
memory usage: 441.5+ KB

J'ai créé une copie dans le jeu de données nettoyé pour conserver les données d'origine intactes. Maintenant, si nous examinons les résultats de la méthode describe(), nous constatons que la colonne RNK est manquante, car elle contient des chaînes de caractères. Elle ne peut donc pas être utilisée pour une analyse statistique, et le nombre de la colonne CNT est inférieur au nombre indiqué dans la colonne BRTH_YR. parce qu'il y a des valeurs nan. En appelant la méthode info() , nous obtenons une description plus détaillée.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
 >>> na_baby_names[na_baby_names.CNT.isnull()].head()
     BRTH_YR    GNDR              ETHCTY     NM  CNT RNK
156      2011  FEMALE            HISPANIC  Riley  NaN  76
473      2011  FEMALE  WHITE NON HISPANIC  Riley  NaN  63
1250     2011  FEMALE  BLACK NON HISPANIC  Riley  NaN  40
1608     2011    MALE            HISPANIC  Riley  NaN  86
1889     2011    MALE  WHITE NON HISPANIC  Riley  NaN  93

Pour résoudre cette question, nous prendrons la valeur moyenne des noms de chaque année pour renseigner les valeurs manquantes. Je sais que ce n'est pas la meilleure approche, mais c'est mieux que de deviner une valeur ou de laisser les champs en nan :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
>>> for y in np.unique(na_baby_names.loc[(na_baby_names.CNT.isnull()), 'BRTH_YR']):
...     na_baby_names.loc[(na_baby_names.CNT.isnull()) & (na_baby_names.BRTH_YR == y), 'CNT'] = na_baby_names.loc[(na_baby_names.BRTH_YR == y), 'CNT'].mean().astype(int)
...
>>> na_baby_names.describe()
          BRTH_YR          CNT
count  8073.000000  8073.000000
mean   2012.522482    34.525579
std       1.118043    39.918905
min    2011.000000    10.000000
25%    2012.000000    13.000000
50%    2013.000000    20.000000
75%    2014.000000    36.000000
max    2014.000000   426.000000

La solution itère sur les années uniques et définit la valeur manquante sur le résultat entier (en coupant la partie décimale) de la moyenne des autres résultats de la même année.

Il est maintenant temps de gérer les valeurs vides dans la colonne RNK. Il existe à nouveau différentes approches en fonction de votre jeu de données. Nous savons ici que la colonne ne doit contenir que des valeurs numériques (ou un nombre entier pour être plus spécifique).

Pour ce faire, nous pouvons remplacer les champs vides par np.nan :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
 >>> na_baby_names.loc[(na_baby_names.RNK == ''), 'RNK'] = np.nan
>>> na_baby_names.loc[(na_baby_names.RNK == ''), 'RNK']
Series([], Name: RNK, dtype: object)
>>> na_baby_names.loc[(na_baby_names.RNK.isnull()), 'RNK']
249      NaN
723      NaN
936      NaN
7888     NaN
8255     NaN
8552     NaN
9079     NaN
9273     NaN
9559     NaN
9890     NaN
10068    NaN
10273    NaN
10567    NaN
10915    NaN
11085    NaN
11285    NaN
11579    NaN
11926    NaN
12107    NaN
12323    NaN
12611    NaN
13170    NaN
13359    NaN
Name: RNK, dtype: object

Cette approche est très basique et ne fonctionne qu'avec une chaîne vide. Si nous avons également différentes chaînes dans la colonne, nous devons rechercher ces valeurs et les définir sur nan individuellement - et ceci est impossible. Heureusement, nous avons une solution pour cela dans Pandas :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
 >>> import pandas as pd
>>> na_baby_names.loc[na_baby_names.NM == 'Avery', 'RNK'] = ''
>>> na_baby_names.RNK = pd.to_numeric(na_baby_names.RNK)
>>> na_baby_names.loc[(na_baby_names.NM == 'Avery'), 'RNK'].head()
249    NaN
723    NaN
936    NaN
7888   NaN
8255   NaN
Name: RNK, dtype: float64

La fonction to_numeric convertit les valeurs d'une colonne spécifiée (Series) ou d'une table (DataFrame) en valeurs numériques et ne déclenche pas d'exception si une valeur non numérique est rencontrée, comme une chaîne de caractères dans le cas de l'exemple.

V. Informations sur les jeux de données

Nous savons comment accéder à des informations telles que la forme ou le nombre d'éléments d'un DataFrame. Mais Pandas offre plus d'informations en utilisant la méthode info(). Celle-ci nous donne également des données sur l’utilisation de la mémoire, ce qui nous aide à identifier de grands ensembles de données et à décider si on filtre sur place ou s’il faut effectuer la purge des données inutiles.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
>>> clean_baby_names.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 8073 entries, 0 to 13961
Data columns (total 6 columns):
BRTH_YR    8073 non-null int64
GNDR       8073 non-null object
ETHCTY     8073 non-null object
NM         8073 non-null object
CNT        8073 non-null int64
RNK        8073 non-null int64
dtypes: int64(3), object(3)
memory usage: 441.5+ KB

Le symbole + indique que l'utilisation réelle de la mémoire peut être supérieure, car Pandas ne compte pas la mémoire utilisée par les valeurs des colonnes avec dtype = object. Mais si nous sommes assez enthousiastes, nous pouvons dire à Pandas de nous donner le véritable usage de la mémoire. Pour cela, nous devons fournir l’argument memory_usage avec la valeur 'deep' à la méthode info() :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

 >>> clean_baby_names.info(memory_usage='deep')
<class 'pandas.core.frame.DataFrame'>
Int64Index: 8073 entries, 0 to 13961
Data columns (total 6 columns):
BRTH_YR    8073 non-null int64
GNDR       8073 non-null object
ETHCTY     8073 non-null object
NM         8073 non-null object
CNT        8073 non-null int64
RNK        8073 non-null int64
dtypes: int64(3), object(3)
memory usage: 1.9 MB

Nous voyons maintenant que notre ensemble de données consomme près de 2 Mo. Ce n'est pas si grave, mais rappelez-vous, nous travaillons avec un exemple de jeu de données. Si nous adoptons des projets de la vie réelle, nous pouvons obtenir des données beaucoup plus volumineuses et dans ce cas, vous devez tenir compte de l'utilisation de la mémoire dans vos applications.

VI. Conclusion

Nous avons vu que les véritables ensembles de données sont imparfaits par défaut. Nous devons prendre soin de valider l'ensemble de données avant de nous y plonger et de procéder à une analyse. Pour cela, nous avons vu comment identifier les doublons et nettoyer les données manquantes.

Dans le prochain article, nous poursuivrons notre cheminement et apprendrons comment agréger nos données. Nous utiliserons ces connaissances pour identifier et éliminer les données aberrantes dans l'ensemble de données, ce qui pourrait fausser notre analyse.

Assurez-vous d'utiliser la recherche de DiscoverSDK pour trouver tous les meilleurs outils de développement en un seul endroit.

VII. Remerciements

Nous remercions Gabor Laszlo Hajba de nous avoir autorisés à publier son tutoriel.

Nous tenons également à remercier vavavoum74 pour la traduction de ce tutoriel, Christophe pour la validation technique et escartefigue pour la relecture orthographique

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2019 Gabor Laszlo Hajba. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.