Tutoriel d’introduction à Pandas

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

Gabor Laszlo Hajba

Traducteur

vavavoum74

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.

Commentez

- Titre : Tutoriel d’introduction à Pandas

- Auteur : Gabor Laszlo Hajba

- Traducteur : vavavoum74

- Parution : 7 septembre 2019

- Licence : 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 et 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.

Sommaire

- I. Accéder aux colonnes

- II. Typos et autres erreurs de saisie

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

- IV. Traitement NaN et N/A

- V. Informations sur les jeux de données

- VI. Conclusion

- VII. Remerciements

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 :

1. >>> baby_names.BRTH_YR.head()

2. 0    2011

3. 1    2011

4. 2    2011

5. 3    2011

6. 4    2011

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

1. >>> import numpy as np

2. >>> np.unique(baby_names.iloc[:, 2])

3. array(['ASIAN AND PACI', 'ASIAN AND PACIFIC ISLANDER', 'BLACK NON HISP',

4. 'BLACK NON HISPANIC', 'HISPANIC', 'WHITE NON HISP',

5. '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 :

1. >>> baby_names.loc[baby_names.ETHCTY == 'ASIAN AND PACI', 'ETHCTY'] = 'ASIAN AND PACIFIC ISLANDER'

2. >>> baby_names.loc[baby_names.ETHCTY == 'BLACK NON HISP', 'ETHCTY'] = 'BLACK NON HISPANIC'

3. >>> baby_names.loc[baby_names.ETHCTY == 'WHITE NON HISP', 'ETHCTY'] = 'WHITE NON HISPANIC'

4. >>> np.unique(baby_names.iloc[:, 2])

5. array(['ASIAN AND PACIFIC ISLANDER', 'BLACK NON HISPANIC', 'HISPANIC',

6. '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 :

1. >>> baby_names.NM.head()

2. 0         GERALDINE

3. 1               GIA

4. 2            GIANNA

5. 3           GISELLE

6. 4             GRACE

7. >>> baby_names.NM = baby_names.NM.str.capitalize()

8. >>> baby_names.NM.head()

9. 0    Geraldine

10. 1          Gia

11. 2       Gianna

12. 3      Giselle

13. 4        Grace

14. 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 :

1. >>> baby_names.duplicated().head()

2. 0    False

3. 1    False

4. 2    False

5. 3    False

6. 4    False

7. dtype: bool

8. >>> baby_names[baby_names.duplicated()].head()

9. BRTH_YR    GNDR              ETHCTY        NM  CNT  RNK

10. 1963     2011  FEMALE  WHITE NON HISPANIC  Scarlett   31   60

11. 1964     2011  FEMALE  WHITE NON HISPANIC    Serena   16   75

12. 1965     2011  FEMALE  WHITE NON HISPANIC    Shaina   16   75

13. 1966     2011  FEMALE  WHITE NON HISPANIC  Shaindel   15   76

14. 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 :

1. >>> baby_names[baby_names.duplicated()].sort_values(by='NM').head()

2. BRTH_YR    GNDR              ETHCTY       NM  CNT  RNK

3. 2582     2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5

4. 4082     2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5

5. 2766     2011  FEMALE            HISPANIC  Aaliyah   63   30

6. 4266     2011  FEMALE            HISPANIC  Aaliyah   63   30

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

1. >>> baby_names[baby_names.duplicated()].describe()

2. BRTH_YR          CNT          RNK

3. count   5889.0  5889.000000  5889.000000

4. mean    2011.0    34.602140    56.494142

5. std        0.0    40.513511    25.012470

6. min     2011.0    10.000000     1.000000

7. 25%     2011.0    13.000000    37.000000

8. 50%     2011.0    20.000000    60.000000

9. 75%     2011.0    36.000000    77.000000

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

1. >>> clean_baby_names = baby_names.drop_duplicates()

2. >>> clean_baby_names.describe()

3. BRTH_YR          CNT          RNK

4. count  8073.000000  8073.000000  8073.000000

5. mean   2012.522482    34.480243    57.272761

6. std       1.118043    39.931078    25.609985

7. min    2011.000000    10.000000     1.000000

8. 25%    2012.000000    13.000000    38.000000

9. 50%    2013.000000    20.000000    59.000000

10. 75%    2014.000000    36.000000    78.000000

11. max    2014.000000   426.000000   102.000000

12. >>> clean_baby_names[clean_baby_names.NM == 'Aaliyah']

13. BRTH_YR    GNDR              ETHCTY       NM  CNT  RNK

14. 1110      2011  FEMALE  BLACK NON HISPANIC  Aaliyah   69    5

15. 1294      2011  FEMALE            HISPANIC  Aaliyah   63   30

16. 8031      2012  FEMALE  BLACK NON HISPANIC  Aaliyah   55   10

17. 8202      2012  FEMALE            HISPANIC  Aaliyah   68   26

18. 10031     2013  FEMALE  BLACK NON HISPANIC  Aaliyah   73    3

19. 10212     2013  FEMALE            HISPANIC  Aaliyah   56   33

20. 12072     2014  FEMALE  BLACK NON HISPANIC  Aaliyah   67    4

21. 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 :

1. >>> clean_baby_names.duplicated().sum()

2. 0

3. >>> clean_baby_names.duplicated(['NM', 'BRTH_YR','ETHCTY']).sum()

4. 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:

1. >>> clean_baby_names[clean_baby_names.duplicated(['NM', 'BRTH_YR','ETHCTY'])].sort_values('NM').head()

2. BRTH_YR    GNDR              ETHCTY      NM  CNT  RNK

3. 13344     2014    MALE            HISPANIC  Alexis   40   68

4. 1132      2011  FEMALE  BLACK NON HISPANIC   Angel   16   39

5. 9551      2012    MALE  WHITE NON HISPANIC   Ariel   20   84

6. 9268      2012    MALE            HISPANIC   Ariel   18   92

7. 1340      2011  FEMALE            HISPANIC   Ariel   17   71

8. >>> clean_baby_names[(clean_baby_names.NM == 'Alexis') & (clean_baby_names.ETHCTY == 'HISPANIC') & (clean_baby_names.BRTH_YR == 2014)]

9. BRTH_YR    GNDR    ETHCTY      NM  CNT  RNK

10. 12272     2014  FEMALE  HISPANIC  Alexis   13   70

11. 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 :

1. >>> clean_baby_names.duplicated(['NM', 'BRTH_YR','ETHCTY','GNDR']).sum()

2. 0

3. _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 :

1. >>> import numpy as np

2. >>> na_baby_names = clean_baby_names.copy()

3. >>> na_baby_names.loc[na_baby_names.NM == 'Riley', 'CNT'] = np.nan

4. >>> na_baby_names.loc[na_baby_names.NM == 'Avery', 'RNK'] = ''

5. >>> na_baby_names.describe()

6. BRTH_YR          CNT

7. count  8073.000000  8049.000000

8. mean   2012.522482    34.527519

9. std       1.118043    39.978346

10. min    2011.000000    10.000000

11. 25%    2012.000000    13.000000

12. 50%    2013.000000    20.000000

13. 75%    2014.000000    36.000000

14. max    2014.000000   426.000000

15. >>> na_baby_names.info()

16. <class 'pandas.core.frame.DataFrame'>

17. Int64Index: 8073 entries, 0 to 13961

18. Data columns (total 6 columns):

19. BRTH_YR    8073 non-null int64

20. GNDR       8073 non-null object

21. ETHCTY     8073 non-null object

22. NM         8073 non-null object

23. CNT        8049 non-null float64

24. RNK        8073 non-null object

25. dtypes: float64(1), int64(1), object(4)

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

1. >>> na_baby_names[na_baby_names.CNT.isnull()].head()

2. BRTH_YR    GNDR              ETHCTY     NM  CNT RNK

3. 156      2011  FEMALE            HISPANIC  Riley  NaN  76

4. 473      2011  FEMALE  WHITE NON HISPANIC  Riley  NaN  63

5. 1250     2011  FEMALE  BLACK NON HISPANIC  Riley  NaN  40

6. 1608     2011    MALE            HISPANIC  Riley  NaN  86

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

1. >>> for y in np.unique(na_baby_names.loc[(na_baby_names.CNT.isnull()), 'BRTH_YR']):

2. ...     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)

3. ...

4. >>> na_baby_names.describe()

5. BRTH_YR          CNT

6. count  8073.000000  8073.000000

7. mean   2012.522482    34.525579

8. std       1.118043    39.918905

9. min    2011.000000    10.000000

10. 25%    2012.000000    13.000000

11. 50%    2013.000000    20.000000

12. 75%    2014.000000    36.000000

13. 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 :

1. >>> na_baby_names.loc[(na_baby_names.RNK == ''), 'RNK'] = np.nan

2. >>> na_baby_names.loc[(na_baby_names.RNK == ''), 'RNK']

3. Series([], Name: RNK, dtype: object)

4. >>> na_baby_names.loc[(na_baby_names.RNK.isnull()), 'RNK']

5. 249      NaN

6. 723      NaN

7. 936      NaN

8. 7888     NaN

9. 8255     NaN

10. 8552     NaN

11. 9079     NaN

12. 9273     NaN

13. 9559     NaN

14. 9890     NaN

15. 10068    NaN

16. 10273    NaN

17. 10567    NaN

18. 10915    NaN

19. 11085    NaN

20. 11285    NaN

21. 11579    NaN

22. 11926    NaN

23. 12107    NaN

24. 12323    NaN

25. 12611    NaN

26. 13170    NaN

27. 13359    NaN

28. 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 :

1. >>> import pandas as pd

2. >>> na_baby_names.loc[na_baby_names.NM == 'Avery', 'RNK'] = ''

3. >>> na_baby_names.RNK = pd.to_numeric(na_baby_names.RNK)

4. >>> na_baby_names.loc[(na_baby_names.NM == 'Avery'), 'RNK'].head()

5. 249    NaN

6. 723    NaN

7. 936    NaN

8. 7888   NaN

9. 8255   NaN

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

1. >>> clean_baby_names.info()

2. <class 'pandas.core.frame.DataFrame'>

3. Int64Index: 8073 entries, 0 to 13961

4. Data columns (total 6 columns):

5. BRTH_YR    8073 non-null int64

6. GNDR       8073 non-null object

7. ETHCTY     8073 non-null object

8. NM         8073 non-null object

9. CNT        8073 non-null int64

10. RNK        8073 non-null int64

11. dtypes: int64(3), object(3)

12. 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() :

1. >>> clean_baby_names.info(memory_usage='deep')

2. <class 'pandas.core.frame.DataFrame'>

3. Int64Index: 8073 entries, 0 to 13961

4. Data columns (total 6 columns):

5. BRTH_YR    8073 non-null int64

6. GNDR       8073 non-null object

7. ETHCTY     8073 non-null object

8. NM         8073 non-null object

9. CNT        8073 non-null int64

10. RNK        8073 non-null int64

11. dtypes: int64(3), object(3)

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