Statistiques descriptives

En python : Pandas etc.

Frédéric Blanchard

Abstract

Module de première année de BUT informatique, Reims. TP de statistiques descriptives.

L’analyse de données contemporaine traite généralement des données avec un nombre important de variables, et de grandes quantités d’individus.

La gestion de ces variables avec des structures de données basiques devient vite compliquée. Le module pandas fait figure de référence quand il s’agit de traiter ces données tabulaires. Il étend les structures de données de base de python en ajoutant un type Series (pour représenter une variable) et un type DataFrame, qui est une sorte de dict dont les valeurs sont des Series. L’usage des data frames est indiqué lorsqu’on doit représenter un tableau de données hétérogènes (c’est à dire dont les variables -en colonnes- sont de natures différentes). Le module seabornpermet quant à lui de produire des graphiques facilement à partir d’un data frame.

Attention : ce travail est à réaliser avec la prochaine version de jupyter au département https://iut-info.univ-reims.fr/notebook/

1 Manipulation de tableaux de données avec pandas

pandas (doc de ref.) inclut des outils d’import et d’export de données, d’accès, de filtrage, et d’analyse de ces données.

La méthode read_csv permet de lire un fichier csv et de créer le data frame correspondant. Le chemin vers le fichier csv peut être une URL.

import pandas
# Choisir ce jeu de données par défaut
df = pandas.read_csv("https://raw.githubusercontent.com/lgreski/pokemonData/master/Pokemon.csv")
# Choisir ce jeu de données si vous êtes expert-dresseur et savez ce que vous faites
# df = pandas.read_csv("./pokemon2.csv")

On peut ensuite “découvrir” le data frame avec :

df.head()
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation
0 1 Bulbasaur Grass Poison 318 45 49 49 65 65 45 1
1 2 Ivysaur Grass Poison 405 60 62 63 80 80 60 1
2 3 Venusaur Grass Poison 525 80 82 83 100 100 80 1
3 4 Charmander Fire 309 39 52 43 60 50 65 1
4 5 Charmeleon Fire 405 58 64 58 80 65 80 1

Il est important de vérifier que les types inférés conviennent :

df.dtypes
ID             int64
Name          object
Form          object
Type1         object
Type2         object
Total          int64
HP             int64
Attack         int64
Defense        int64
Sp. Atk        int64
Sp. Def        int64
Speed          int64
Generation     int64
dtype: object

Les noms des variables :

df.columns
Index(['ID', 'Name', 'Form', 'Type1', 'Type2', 'Total', 'HP', 'Attack',
       'Defense', 'Sp. Atk', 'Sp. Def', 'Speed', 'Generation'],
      dtype='object')

Pour obtenir les dimensions du data frame :

print("Nombre de lignes : ", len(df))
print("Nombre de colonnes : ", len(df.columns))
Nombre de lignes :  1045
Nombre de colonnes :  13

On peut ensuite accéder à des colonnes ou des lignes complètes :

df['Name'].head(10)
0     Bulbasaur
1       Ivysaur
2      Venusaur
3    Charmander
4    Charmeleon
5     Charizard
6      Squirtle
7     Wartortle
8     Blastoise
9      Caterpie
Name: Name, dtype: object

La transformer en liste :

df['Name'].head(10).tolist()
['Bulbasaur',
 'Ivysaur',
 'Venusaur',
 'Charmander',
 'Charmeleon',
 'Charizard',
 'Squirtle',
 'Wartortle',
 'Blastoise',
 'Caterpie']

Accès à une ligne :

df.loc[12]
ID                13
Name          Weedle
Form                
Type1            Bug
Type2         Poison
Total            195
HP                40
Attack            35
Defense           30
Sp. Atk           20
Sp. Def           20
Speed             50
Generation         1
Name: 12, dtype: object

Conversion en tuple :

tuple(df.loc[12])
(13, 'Weedle', ' ', 'Bug', 'Poison', 195, 40, 35, 30, 20, 20, 50, 1)

Ou en dict:

dict(df. iloc[12])
{'ID': 13,
 'Name': 'Weedle',
 'Form': ' ',
 'Type1': 'Bug',
 'Type2': 'Poison',
 'Total': 195,
 'HP': 40,
 'Attack': 35,
 'Defense': 30,
 'Sp. Atk': 20,
 'Sp. Def': 20,
 'Speed': 50,
 'Generation': 1}

On peut aussi accéder directement à une valeur, grâce aux indices :

df.iloc[12, 0]
13

Ou avec les noms (clés) :

df.loc[12, 'ID']
13
df.loc[[9, 13], ['Name', 'Attack']]
Name Attack
9 Caterpie 30
13 Kakuna 25
df.loc[10:20,['Name', 'Attack']]
Name Attack
10 Metapod 20
11 Butterfree 45
12 Weedle 35
13 Kakuna 25
14 Beedrill 90
15 Pidgey 45
16 Pidgeotto 60
17 Pidgeot 80
18 Rattata 56
19 Raticate 81
20 Spearow 60

On même effectuer des requêtes pour sélectionner des sous-parties du tableau :

df[df['Attack']>160]['Name'].tolist()
['Deoxys',
 'Rampardos',
 'Kyurem',
 'Mewtwo',
 'Heracross',
 'Tyranitar',
 'Banette',
 'Groudon',
 'Rayquaza',
 'Garchomp',
 'Gallade',
 'Kartana',
 'Necrozma',
 'Zacian',
 'Calyrex']

Explications :

df['Attack']>160
0       False
1       False
2       False
3       False
4       False
        ...  
1040    False
1041    False
1042    False
1043     True
1044    False
Name: Attack, Length: 1045, dtype: bool
df[df['Attack']>160]
ID Name Form Type1 Type2 Total HP Attack Defense Sp. Atk Sp. Def Speed Generation
389 386 Deoxys Attack Forme Psychic 600 50 180 20 180 20 150 3
414 409 Rampardos Rock 495 97 165 60 65 50 58 4
666 646 Kyurem Black Kyurem Dragon Ice 700 125 170 100 120 90 95 5
686 150 Mewtwo Mega Mewtwo X Psychic Fighting 780 106 190 100 154 100 130 6
691 214 Heracross Mega Heracross Bug Fighting 600 80 185 115 40 105 75 6
693 248 Tyranitar Mega Tyranitar Rock Dark 700 100 164 150 95 120 71 6
706 354 Banette Mega Banette Ghost 555 64 165 75 93 83 75 6
714 383 Groudon Primal Groudon Ground Fire 770 100 180 160 150 90 90 6
715 384 Rayquaza Mega Rayquaza Dragon Flying 780 105 180 100 180 100 115 6
717 445 Garchomp Mega Garchomp Dragon Ground 700 108 170 115 120 95 92 6
720 475 Gallade Mega Gallade Psychic Fighting 618 68 165 95 65 115 110 6
911 798 Kartana Grass Steel 570 59 181 131 59 31 109 7
916 800 Necrozma Ultra Necrozma Psychic Dragon 754 97 167 97 167 97 129 7
1028 888 Zacian Crowned Sword Fairy Steel 720 92 170 115 80 115 148 8
1043 898 Calyrex Ice Rider Psychic Ice 680 100 165 150 85 130 50 8
df[df['Attack']>160]['Name']
389        Deoxys
414     Rampardos
666        Kyurem
686        Mewtwo
691     Heracross
693     Tyranitar
706       Banette
714       Groudon
715      Rayquaza
717      Garchomp
720       Gallade
911       Kartana
916      Necrozma
1028       Zacian
1043      Calyrex
Name: Name, dtype: object

On peut combiner (mais attention, pandas a ses propres opérateurs logiques, et les parenthèses sont indispensables) :

selection = df[(df['Attack']>130) & (df['Defense']>140)]['Name']

print("Les pokemons ayant plus 130 en attaque et plus de 140 en défense sont : ", 
      ' '.join(selection.tolist()))
Les pokemons ayant plus 130 en attaque et plus de 140 en défense sont :  Tyranitar Aggron Metagross Groudon Stakataka Melmetal Calyrex

On peut ajouter des variables à un data frame existant :

df['diff_ad'] = [0] * len(df)

et aussi appliquer des fonctions à chaque élément d’une Series grâce à la méthode apply() :

df['diff_ad'] = (df['Attack']-df['Defense']).apply(abs)

Ici, la fonction abs est appliquée à la Series obtenue par différence des Séries Attack et Defense.

df['diff_ad'].head(10)
0     0
1     1
2     1
3     9
4     6
5     6
6    17
7    17
8    17
9     5
Name: diff_ad, dtype: int64
df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1045 entries, 0 to 1044
Data columns (total 14 columns):
 #   Column      Non-Null Count  Dtype 
---  ------      --------------  ----- 
 0   ID          1045 non-null   int64 
 1   Name        1045 non-null   object
 2   Form        1045 non-null   object
 3   Type1       1045 non-null   object
 4   Type2       1045 non-null   object
 5   Total       1045 non-null   int64 
 6   HP          1045 non-null   int64 
 7   Attack      1045 non-null   int64 
 8   Defense     1045 non-null   int64 
 9   Sp. Atk     1045 non-null   int64 
 10  Sp. Def     1045 non-null   int64 
 11  Speed       1045 non-null   int64 
 12  Generation  1045 non-null   int64 
 13  diff_ad     1045 non-null   int64 
dtypes: int64(10), object(4)
memory usage: 114.4+ KB

Exercice « MAIS POURQUOI ON A FAIT TOUT ÇA ? »

Les modules pandas et numpy incluent la plupart des fonctions que nous avons impléentées lors des séquences précédentes…

À l’aide la documentation et d’un peu de flair, retrouver les fonctions/méthodes correspondant à celles des TP précédents. S’il en manque, on pourra recourir au module statistics.

2 Profiling

La description rudimentaire des variables d’un data frame peut facilement s’automatiser. Il existe d’ailleurs des modules dédiés. Ces modules, utiles en première intention ne remplaceront hélas pas une étude approfondie « manuelle ».

Import du module pandas-profiling :

import pandas_profiling
report = pandas_profiling.ProfileReport(df)
report.to_file(output_file='./rapport.html')

Ouvrir rapport.html avec votre navigateur préféré…

3 Activités pratiques

Exercice PODIUMS

Écrire une fonction podiums() qui détermine, pour chaque variable parmi Total, HP, Attack, Defense, Sp. Atk, Sp. Def et Speed, les trois meilleurs pokemons.

Exercice ÉVALUATION D’UN DECK

Écrire une fonction eval_dec(df, selec) qui évalue un sous-ensemble de pokemons (défini dans la liste selec) du data frame df, en calculant :

et en déterminant le quantile de ce deck moyen, pour chacune de ces caractéristiques

4 Pour aller plus loin

4.1 Outils avancés de production graphique

Les bibliothèques suivantes permettent de produire des graphiques avancés, pour certains dynamiques et intéractifs.

4.2 Outils de construction de dashboard