Notebook originel: pokemon.ipynb
Pokémon Go! (démonstration Pandas/Seaborn)🔗
Voici un exemple d’utilisation des libraries Pandas (manipulation de données hétérogène) et Seaborn (visualisations statistiques), sur le Pokémon dataset d’Alberto Barradas.
Références:
[1]:
import warnings
warnings.simplefilter(action='ignore', category=FutureWarning)
import pandas as pd # Emit FutureWarnings
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline
Lecture et préparation des données🔗
Pandas fournit des méthodes de lecture des données à partir de nombreux formats, dont les données Comma Separated Values:
[2]:
df = pd.read_csv('./Pokemon.csv', index_col='Name') # Indexation sur le nom (unique)
df.info() # Informations générales
<class 'pandas.core.frame.DataFrame'>
Index: 800 entries, Bulbasaur to Volcanion
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 # 800 non-null int64
1 Type 1 800 non-null object
2 Type 2 414 non-null object
3 Total 800 non-null int64
4 HP 800 non-null int64
5 Attack 800 non-null int64
6 Defense 800 non-null int64
7 Sp. Atk 800 non-null int64
8 Sp. Def 800 non-null int64
9 Speed 800 non-null int64
10 Generation 800 non-null int64
11 Legendary 800 non-null bool
dtypes: bool(1), int64(9), object(2)
memory usage: 75.8+ KB
Les premières lignes du DataFrame (tableau 2D) qui en résulte:
[3]:
df.head(10) # Les 10 premières lignes
[3]:
| # | Type 1 | Type 2 | Total | HP | Attack | Defense | Sp. Atk | Sp. Def | Speed | Generation | Legendary | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Name | ||||||||||||
| Bulbasaur | 1 | Grass | Poison | 318 | 45 | 49 | 49 | 65 | 65 | 45 | 1 | False |
| Ivysaur | 2 | Grass | Poison | 405 | 60 | 62 | 63 | 80 | 80 | 60 | 1 | False |
| Venusaur | 3 | Grass | Poison | 525 | 80 | 82 | 83 | 100 | 100 | 80 | 1 | False |
| VenusaurMega Venusaur | 3 | Grass | Poison | 625 | 80 | 100 | 123 | 122 | 120 | 80 | 1 | False |
| Charmander | 4 | Fire | NaN | 309 | 39 | 52 | 43 | 60 | 50 | 65 | 1 | False |
| Charmeleon | 5 | Fire | NaN | 405 | 58 | 64 | 58 | 80 | 65 | 80 | 1 | False |
| Charizard | 6 | Fire | Flying | 534 | 78 | 84 | 78 | 109 | 85 | 100 | 1 | False |
| CharizardMega Charizard X | 6 | Fire | Dragon | 634 | 78 | 130 | 111 | 130 | 85 | 100 | 1 | False |
| CharizardMega Charizard Y | 6 | Fire | Flying | 634 | 78 | 104 | 78 | 159 | 115 | 100 | 1 | False |
| Squirtle | 7 | Water | NaN | 314 | 44 | 48 | 65 | 50 | 64 | 43 | 1 | False |
Le format est ici simple:
nom du Pokémon (utilisé comme indice) et son n° (notons que le n° n’est pas unique)
type primaire et éventuellement secondaire str
différentes caractéristiques int (p.ex. points de vie, niveaux d’attage et défense, vitesse, génération)
type légendaire bool
Nous appliquons les filtres suivants directement sur le dataframe (inplace=True):
simplifier le nom des mega pokémons
remplacer les
NaNde la colonne «Type 2»éliminer les colonnes « # » et « Sp. »
[4]:
df.set_index(df.index.str.replace(".*(?=Mega)", ''), inplace=True) # Supprime la chaîne avant Mega
df['Type 2'].fillna('', inplace=True) # Remplace NaN par ''
df.drop(['#'] + [ col for col in df.columns if col.startswith('Sp.')],
axis=1, inplace=True) # "Laisse tomber" les colonnes commençant par 'Sp.'
df.head(10) # Les 5 premières lignes
[4]:
| Type 1 | Type 2 | Total | HP | Attack | Defense | Speed | Generation | Legendary | |
|---|---|---|---|---|---|---|---|---|---|
| Name | |||||||||
| Bulbasaur | Grass | Poison | 318 | 45 | 49 | 49 | 45 | 1 | False |
| Ivysaur | Grass | Poison | 405 | 60 | 62 | 63 | 60 | 1 | False |
| Venusaur | Grass | Poison | 525 | 80 | 82 | 83 | 80 | 1 | False |
| Mega Venusaur | Grass | Poison | 625 | 80 | 100 | 123 | 80 | 1 | False |
| Charmander | Fire | 309 | 39 | 52 | 43 | 65 | 1 | False | |
| Charmeleon | Fire | 405 | 58 | 64 | 58 | 80 | 1 | False | |
| Charizard | Fire | Flying | 534 | 78 | 84 | 78 | 100 | 1 | False |
| Mega Charizard X | Fire | Dragon | 634 | 78 | 130 | 111 | 100 | 1 | False |
| Mega Charizard Y | Fire | Flying | 634 | 78 | 104 | 78 | 100 | 1 | False |
| Squirtle | Water | 314 | 44 | 48 | 65 | 43 | 1 | False |
Accès aux données🔗
Pandas propose de multiples façons d’accéder aux données d’un DataFrame, ici:
via le nom (indexé):
[5]:
df.loc['Bulbasaur', ['Type 1', 'Type 2']] # Seulement 2 colonnes
[5]:
Type 1 Grass
Type 2 Poison
Name: Bulbasaur, dtype: object
par sa position dans la liste:
[6]:
df.iloc[-5:, :2] # Les 5 dernières lignes, et les 2 premières colonnes
[6]:
| Type 1 | Type 2 | |
|---|---|---|
| Name | ||
| Diancie | Rock | Fairy |
| Mega Diancie | Rock | Fairy |
| HoopaHoopa Confined | Psychic | Ghost |
| HoopaHoopa Unbound | Psychic | Dark |
| Volcanion | Fire | Water |
par une sélection booléenne, p.ex. tous les pokémons légendaires de type herbe:
[7]:
df[df['Legendary'] & (df['Type 1'] == 'Grass')]
[7]:
| Type 1 | Type 2 | Total | HP | Attack | Defense | Speed | Generation | Legendary | |
|---|---|---|---|---|---|---|---|---|---|
| Name | |||||||||
| ShayminLand Forme | Grass | 600 | 100 | 100 | 100 | 100 | 4 | True | |
| ShayminSky Forme | Grass | Flying | 600 | 100 | 103 | 75 | 127 | 4 | True |
| Virizion | Grass | Fighting | 580 | 91 | 90 | 72 | 108 | 5 | True |
Quelques statistiques🔗
[8]:
df[['Total', 'HP', 'Attack', 'Defense']].describe() # Description statistique des différentes colonnes
[8]:
| Total | HP | Attack | Defense | |
|---|---|---|---|---|
| count | 800.00000 | 800.000000 | 800.000000 | 800.000000 |
| mean | 435.10250 | 69.258750 | 79.001250 | 73.842500 |
| std | 119.96304 | 25.534669 | 32.457366 | 31.183501 |
| min | 180.00000 | 1.000000 | 5.000000 | 5.000000 |
| 25% | 330.00000 | 50.000000 | 55.000000 | 50.000000 |
| 50% | 450.00000 | 65.000000 | 75.000000 | 70.000000 |
| 75% | 515.00000 | 80.000000 | 100.000000 | 90.000000 |
| max | 780.00000 | 255.000000 | 190.000000 | 230.000000 |
[9]:
df.loc[df['HP'].idxmax()] # Pokémon ayant le plus de points de vie
[9]:
Type 1 Normal
Type 2
Total 540
HP 255
Attack 10
Defense 10
Speed 55
Generation 2
Legendary False
Name: Blissey, dtype: object
[10]:
df.sort_values('Speed', ascending=False).head(3) # Les 3 pokémons plus rapides
[10]:
| Type 1 | Type 2 | Total | HP | Attack | Defense | Speed | Generation | Legendary | |
|---|---|---|---|---|---|---|---|---|---|
| Name | |||||||||
| DeoxysSpeed Forme | Psychic | 600 | 50 | 95 | 90 | 180 | 3 | True | |
| Ninjask | Bug | Flying | 456 | 61 | 90 | 45 | 160 | 3 | False |
| DeoxysNormal Forme | Psychic | 600 | 50 | 150 | 50 | 150 | 3 | True |
Statistiques selon le statut « légendaire »:
[11]:
legendary = df.groupby('Legendary')
legendary.size()
[11]:
Legendary
False 735
True 65
dtype: int64
[12]:
legendary['Total', 'HP', 'Attack', 'Defense', 'Speed'].mean()
[12]:
| Total | HP | Attack | Defense | Speed | |
|---|---|---|---|---|---|
| Legendary | |||||
| False | 417.213605 | 67.182313 | 75.669388 | 71.559184 | 65.455782 |
| True | 637.384615 | 92.738462 | 116.676923 | 99.661538 | 100.184615 |
Visualisation🔗
Pandas intègre de nombreuses fonctions de visualisation interfacées à matplotlib.
[13]:
ax = df.plot.scatter(x='Attack', y='Defense', s=df['HP'], c='Speed', cmap='plasma')
ax.figure.set_size_inches((8, 6))
[14]:
fig, (ax1, ax2) = plt.subplots(1, 2, subplot_kw={"aspect": 'equal'}, figsize=(10, 6))
df['Type 1'].value_counts().plot.pie(ax=ax1, autopct='%.0f%%')
df['Type 2'].value_counts().plot.pie(ax=ax2, autopct='%.0f%%')
fig.tight_layout()
Il est également possible d’utiliser la librairie seaborn, qui s’interface naturellement avec Pandas.
[15]:
pok_type_colors = { # http://bulbapedia.bulbagarden.net/wiki/Category:Type_color_templates
'Grass': '#78C850',
'Fire': '#F08030',
'Water': '#6890F0',
'Bug': '#A8B820',
'Normal': '#A8A878',
'Poison': '#A040A0',
'Electric': '#F8D030',
'Ground': '#E0C068',
'Fairy': '#EE99AC',
'Fighting': '#C03028',
'Psychic': '#F85888',
'Rock': '#B8A038',
'Ghost': '#705898',
'Ice': '#98D8D8',
'Dragon': '#7038F8',
'Dark': '#705848',
'Steel': '#B8B8D0',
'Flying': '#A890F0',
}
[16]:
ax = sns.countplot(x='Generation', hue='Type 1', palette=pok_type_colors, data=df)
ax.figure.set_size_inches((14, 6))
ax.legend(ncol=3, title='Type 1');
[17]:
ax = sns.boxplot(x='Generation', y='Total', data=df, color='0.5');
sns.swarmplot(x='Generation', y='Total', data=df, color='0.2', alpha=0.8)
ax.figure.set_size_inches((14, 6))
/home/ycopin/.virtualenvs/Python3.10/lib/python3.10/site-packages/seaborn/categorical.py:3544: UserWarning: 13.3% of the points cannot be placed; you may want to decrease the size of the markers or use stripplot.
warnings.warn(msg, UserWarning)
/home/ycopin/.virtualenvs/Python3.10/lib/python3.10/site-packages/seaborn/categorical.py:3544: UserWarning: 6.6% of the points cannot be placed; you may want to decrease the size of the markers or use stripplot.
warnings.warn(msg, UserWarning)
[18]:
ax = sns.violinplot(x="Type 1", y="Attack", data=df, hue="Legendary", split=True, inner='quart')
ax.figure.set_size_inches((14, 6))
[19]:
df2 = df.drop(['Total', 'Generation', 'Legendary'], axis=1)
sns.pairplot(df2, markers='.', kind='reg');
[20]:
ax = sns.heatmap(df2.corr(), annot=True,
cmap='RdBu_r', center=0, cbar_kws={'label': 'Correlation coefficient'})
ax.set_aspect('equal')
[21]:
sns.catplot(x='Generation', y='Attack', data=df,
hue='Type 1', palette=pok_type_colors, col='Type 1', col_wrap=3, kind='swarm');
Cette page a été générée à partir de
pokemon.ipynb.