Les données sont transversales dans les flux de travail en sciences. Elles alimentent l'analyse, et la modélisation. Les résultats qui en découlent sont aussi des données qui peuvent alimenter les travaux subséquents. Une bonne organisation des données facilitera le flux de travail.
Dicton. Proportions de temps voué aux calcul scientifique: 80% de nettoyage de données mal organisées, 20% de calcul.
Qu'est-ce qu'une donnée? De manière abstraite, il s'agit d'une valeur associée à une variable. Une variable peut être une dimension, une date, une couleur, le résultat d'un test statistique, à laquelle on attribue la valeur quantitative ou qualitative d'un chiffre, d'une chaîne de charactère, d'un symbole conventionné, etc. Par exemple, lorsque vous commandez un café latte végane, au latte est la valeur que vous attribuez à la variable type de café, et végane est la valeur de la variable type de lait.
Ce chapitre traite de l'importation, l'utilisation et l'exportation de données structurées, en Python, sous forme de vecteurs, matrices, tableaux et ensemble de tableaux (bases de données).
Bien qu'il soit toujour préférable d'organiser les structures qui accueilleront les données d'une expérience avant-même de procéder à la collecte de données, l'analyste doit s'attendre à réorganiser ses données en cours de route. Or, des données bien organisées au départ faciliteront aussi leur réoganisation.
Ce chapitre débute avec quelques définitions: les données et leurs types, les vecteurs, les matrices, les tableaux et les bases de données, ainsi que leur signification en Python. Puis nous verrons comment organiser un tableau selon quelques règles simples, mais importantes pour éviter les erreurs et les opérations fastidieuses pour reconstruire un tableau mal conçu. Ensuite, nous traiterons des formats de tableau courrant, pour enfin passer à l'utilisation de pandas
, une bibliothèque Python utile pour effectuer des opérations sur les tableaux.
Dans la section précédente, nous avons survoler différents types d'objets: réels, entiers, chaînes de caractères et booléens. Les données peuvent appartenir à d'autres types: dates, catégories ordinales (ordonnées: faible, moyen, élevé) et nominales (non ordonnées: espèces, cultivars, couleurs, unité pédodlogique, etc.).
Dans la section d'introduction à Python, nous avons vu comment Python permet d'organiser des collections d'objets. Bien qu'il soit important de connaître leur existance pour apprendre à maîtriser Python, les listes et les dictionnaires sont peu pratiques pour le calcul. Par exemple, Python ne permet pas d'effectuer les opérations entre les scalaires et les listes.
In [1]:
ma_liste = [1, 2, 3, 4]
ma_liste + 1
Pour les listes, il faudrait passer par des boucles...
In [2]:
ma_liste_2 = []
for i in range(len(ma_liste)):
ma_liste_2.append(ma_liste[i] + 1)
ma_liste_2
Out[2]:
... ce qui est loin d'ête pratique. On préférera généralement utiliser des vecteurs, qui, grâce à la vectorisation, peuvent être soumis à des opérations avec des scalaires. Les vecteurs sont accessibles via la bibliothèque numpy
. Par convention, les fonctions de numpy
sont importés dans une instance np
.
In [3]:
import numpy as np
mon_vecteur = np.array([1, 2, 3, 4])
mon_vecteur + 1
Out[3]:
Contrairement à une liste, un vecteur contient obligatoirement des valeurs même type. À défaut de définir un même type, numpy
transformera chaque valeur en chaîne de caractère.
In [4]:
[1, 2, 3, 'grenouille'] # liste
Out[4]:
In [5]:
np.array([1, 2, 3, 'grenouille']) # vecteur
Out[5]:
In [6]:
np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
Out[6]:
In [7]:
np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9]],
[[10, 11, 12], [13, 14, 15], [16, 17, 18]]])
Out[7]:
Une matrice 2D peut indiquer l'élévation d'un point dans l'espace x, y. En 3D, vous pouvez inclure non seulement l'élévation, mais aussi l'épaisseur de solet d'autres variables. Ajouter une évolution dans le temps et vous obtenez une matrice 4D.
Vous ne pourrez toutefois pas ajouter une couche d'une variable catégorielle dans une matrice numérique: comme les vecteurs, les matrices ne contiennent qu'un seul type de données.
Elles sont utilisées surtout en modélisation. En analyse de données, on préférera les tableaux.
De manière générale, un tableau de données est une organisation de données en deux dimensions, comportant des lignes et des colonnes. Il est préférable de respecter la convention selon laquelle les lignes sont des observations et les colonnes sont des variables. Ainsi, un tableau est une collection de vecteurs de même longueur, chaque vecteur représentant une variable. Chaque variable est libre de prendre le type de données approprié. La position d'une donnée dans le vecteur correspond à une observation.
Imaginez que vous consignez des données de différents sites (A, B et C), et que chaque site possède ses propres caractéristiques. Il est redondant de décrire le site pour chaque observation. Vous préférerez créer deux tableaux: un pour décrire vos observations, et un autre pour décrire les sites. De cette manière, vous créez une collection de tableaux intereliés: une base de données.
Dans Python, les données structurées en tableaux, ainsi que les opérations sur les tableaux, peuvent être gérés grâe à la bibliothèque pandas
. Mais avant de se lancer dans l'utilisation de pandas
, voyons quelques règles à suivre pour bien structurer ses données.
Afin de reprérer chaque cellule d'un tableau, on attribue à chaque lignes et à chaque colonne colonnes un identifiant unique, que l'on nomme indice pour les lignes et entête pour les colonnes.
*Règle no 1. Une variable par colonne, une observation par ligne.*
Les unités expérimentales sont décrits par une ou plusieurs variables par des chiffres ou des lettres. Chaque variable devrait être présente en une seule colonne, et chaque ligne devrait correspondre à une unité expérimentale où ces variables ont été mesurées. La règle parait simple, mais elle est rarement respectée. Prenez par exemple le tableau suivant.
Site | Traitement A | Traitement B | Traitement C |
---|---|---|---|
Sainte-Zéphirine | 4.1 | 8.2 | 6.8 |
Sainte-Aurélie | 5.8 | 5.9 | NA |
Saint-Serge-Étienne | 2.9 | 3.4 | 4.6 |
Tableau 1. Rendements obtenus sur les sites expérimentaux selon les traitements.
Qu'est-ce qui cloche avec ce tableau? Chaque ligne est une observation, mais contient plussieurs observations d'une même variable, le rendement, qui devient étalé sur plusieurs colonnes. À bien y penser, le type de traitement est une variable et le rendement en est une autre:
Site | Traitement | Rendement |
---|---|---|
Sainte-Zéphirine | A | 4.1 |
Sainte-Zéphirine | B | 8.2 |
Sainte-Zéphirine | C | 6.8 |
Sainte-Aurélie | A | 5.8 |
Sainte-Aurélie | B | 5.9 |
Sainte-Aurélie | C | NA |
Saint-Serge-Étienne | A | 2.9 |
Saint-Serge-Étienne | B | 3.4 |
Saint-Serge-Étienne | C | 4.6 |
Tableau 2. Rendements obtenus sur les sites expérimentaux selon les traitements.
Plus précisément, l'expression à bien y penser suggère une réflexion sur la signification des données. Certaines variables peuvent parfois être intégrées dans une même colonne, parfois pas. Par exemple, les concentrations en cuivre, zinc et plomb dans un sol contaminé peuvent être placés dans la même colonne "Concentration" ou déclinées en plusieurs colonnes Cu, Zn et Pb. La première version trouvera son utilité pour des créer des graphiques (chapitre 5), alors que la deuxième favorise le traitement statistique (chapitre 4).
*Règle no 2. Ne pas répéter les informations.*
Rerpenons la même expérience. Supposons que vous mesurez la précipitation à l'échelle du site.
Site | Traitement | Rendement | Précipitations |
---|---|---|---|
Sainte-Zéphirine | A | 4.1 | 813 |
Sainte-Zéphirine | B | 8.2 | 813 |
Sainte-Zéphirine | C | 6.8 | 813 |
Sainte-Aurélie | A | 5.8 | 642 |
Sainte-Aurélie | B | 5.9 | 642 |
Sainte-Aurélie | C | NA | 642 |
Saint-Serge-Étienne | A | 2.9 | 1028 |
Saint-Serge-Étienne | B | 3.4 | 1028 |
Saint-Serge-Étienne | C | 4.6 | 1028 |
Tableau 3. Rendements obtenus sur les sites expérimentaux selon les traitements.
Segmenter l'information en deux tableaux serait préférable.
Site | Précipitations |
---|---|
Sainte-Zéphirine | 813 |
Sainte-Aurélie | 642 |
Saint-Serge-Étienne | 1028 |
Tableau 4. Précipitations sur les sites expérimentaux.
Les tableaux 2 et 4, ensemble, forment une base de données (collection organisée de tableaux).
*Règle no 3. Ne pas bousiller les données.*
Par exemple.
commentaire_pH
.St-Ours
et Saint-Ours
seront traitées comme deux catégories distinctes. De même, les cellules correspondant à des valeurs manquantes ne devraient pas être inscrite parfois avec une cellule vide, parfois avec un point, parfois avec un tiret ou avec la mention NA
. Le plus simple est de laisser systématiquement ces cellules vides.Plusieurs outils sont à votre disposition pour créer des tableaux. Je vous présente ici les plus communs.
Microsoft Excel est un logiciel de type tableur, ou chiffrier électronique. L'ancien format xls a été remplacé par le format xlsx avec l'arrivée de Microsoft Office 2010. Il s'agit d'un format propriétaire, dont l'alternative libre la plus connue est le format ods, popularisé par la suite bureautique LibreOffice. Les formats xls, xlsx ou ods sont davantage utilisés comme outils de calcul que d'entreposage de données. Ils contiennent des formules, des graphiques, du formattage de cellule, etc. Je ne les recommande pas pour stocker des données.
Le format csv, pour comma separated values, est un fichier texte, que vous pouvez ouvrir avec n'importe quel éditeur de texte brut (Bloc note, Atom, Notepad++, etc.). Chaque colonne doit être délimitée par un caractère cohérent (conventionnellement une virgule, mais en français un point-virgule ou une tabulation pour éviter la confusion avec le séparateur décimal) et chaque ligne du tableau est un retour de ligne. Il est possible d'ouvrir et d'éditer les fichiers csv dans un éditeur texte, mais il est plus pratique de les ouvrir avec des tableurs (LibreOffice Calc, Microsoft Excel, Google Sheets, etc.).
Encodage. Puisque le format csv est un fichier texte, un souci particulier doit être porté sur la manière dont le texte est encodé. Les caractères accentués pourrait être importer incorrectement si vous importez votre tableau en spécifiant le mauvais encodage. Pour les fichiers en langues occidentales, l'encodage UTF-8 devrait être utilisé. Toutefois, par défaut, Excel utilise un encodage de Microsoft. Si le csv a été généré par Excel, il est préférable de l'ouvrir avec votre éditeur texte et de l'enregistrer dans l'encodage UTF-8.
Comme le format csv, le format json indique un fichier en texte clair. Il est utilisé davantage pour le partage de données des applications web. En analyse et modélisation, ce format est surtout utilisé pour les données géoréférencées. L'encodage est géré de la même manière qu'un fichier csv.
SQLite est une application pour les bases de données relationnelles de type SQL qui n'a pas besoin de serveur pour fonctionner. Les bases de donnnées SQLite sont encodés dans des fichiers portant l'extension db, qui peuvent être facilement partagés.
En csv pour les petits tableaux, en sqlite pour les bases de données plus complexes. Ce cours se concentre toutefois sur les données de type csv.
La manière la plus sécure pour entreposer ses données est de les confiner dans une base de données sécurisée sur un serveur sécurisé dans un environnement sécurisé. C'est aussi la manière la moins accessible. Des espaces de stockage nuagiques, comme Dropbox ou une option similaire, peuvent être pratiques pour les backups et le partage des données avec une équipe de travail (qui risque en retour de bousiller vos données). Le suivi de version est possible chez certains fournisseurs d'espace de stockage. Mais pour un suivi de version plus rigoureux, les espaces de développement (comme GitHub) sont plus appropriés: nous verrons d'ailleurs, plus loin dans le manuel, comment un projet de recherche peut tirer profit de GitHub. Dans tous les cas, il est important de garder (1) des copies anciennes pour y revenir en cas d'erreurs et (2) un petit fichier décrivant les changements effectués sur les données.
pandas
La bibliothèque pandas
est d'une aide préciseuse pour l'analyse de données en Python. Elle permet d'importer des données dans votre session de travail, de les explorer, de les transformer et de les exporter. Les tableaux de classe pandas
peuvent être manipulés pour l'analyse et la modélisation grâce à d'autres bibliothèques, dont plusieurs sont couvertes dans ce manuel (statsmodels
, scikit-learn
, scikit-bio
, etc.). Cette section se veut une brève introduction à pandas
. Elle est loin de couvrir les nombreuses fonctionnalités qui sont offertes, qui sont couvertes de manière plus exhaustive dans le livre Python for Data Analysis (McKinney, 2016).
pandas.DataFrame
Le type d'objet central de pandas
est le DataFrame
, ou tableau. Des tableaux peuvent être importés depuis des fichiers csv, json ou depuis des bases de données. Importons notre Tableau 1 spécifié en format csv avec un ;
en guise de délimiteur de colonne.
Que ce soit pour inspecter vos donner, pour effectuer des calculs sur celles-ci ou en faire des graphiques, vous devez être en mesure de sélectionner les lignes et les colonnes visées. Dans cette section, nous couvrirons comment sélectionner les données, des manières de les filtrer, les trier, et effectuer des opérations mathématiques sur celles-ci.
Pour effectuer ces opérations, utilisons un tableau de données de culture de la chicouté (Rubus chamaemorus), un petit fruit nordique, tiré de Parent et al. (2013).
In [8]:
import pandas as pd
Pour importer un tableau csv
, on utilise la fonction pandas.read_csv
.
In [9]:
chicoute = pd.read_csv('data/chicoute.csv', sep=';', header=0, index_col=0, na_values='',
encoding='utf-8')
Les principaux arguments ont été spécifiés, bien qu'ils auraient pu être laissés par défaut.
filepath_or_buffer
: le chemin vers le fichier. Ce chemin peut aussi bien être une adresse locale (telle que définit dans cet exemple) qu'une adresse internet (https://...).sep
: le symbole délimitant les colonnes.header
: le numéro de ligne utilisé pour le nom de l'entête.index_col
: le numéro de colonne utilisé pour l'index. Si False
est spécifié, l'index de la ligne est automatiquement imputé. La valeur de l'argument est de 0, puisque l'identifiant unique du tableau chicoute
est positionné à la première colonne (Python comment à compter à 0).na_values
: le symbole spécifiant une valeur manquante. L'argument na_values=''
signifie que les cellules vides sont des données manquantes.encoding
: le format d'encodage du fichier.D'autres arguments peuvent être spécifiés au besoin, et les répéter ici dupliquerait l'information de la documentation de la fonction read_csv
de pandas
. La documentation de pandas
présente d'autres fonctions accessibles pour charger des données formattées en json, tsv, etc.
La méthode head
permet d'afficher le haut du tableau, tout comme la méthode tail
permet d'en afficher le bas.
In [10]:
chicoute.head(3)
Out[10]:
Conformément aux règles de construction d'un tableau, pandas
demande à ce que chaque colonne possède une entête unique, comme chaque ligne possède un indice unique, ce qui peut être vérifié avec les méthodes descriptives columns
et index
.
In [11]:
print("Entêtes:", chicoute.columns)
print("Indices:", chicoute.index)
DataFrame
et les Series
Dans un DataFrame
de pandas
, chaque colonne est une Series
, à laquelle nous accédons ici avec un point suivi du nom de la colonne (nous verons plus tard comment soutirer précisément des éléments d'un tableau). Une Series
peut être utilisée sans DataFrame
, mais vous utiliserez probablement les Series
en tant que colonnes d'un tableau.
La fonction type
permet d'identifier le type de l'objet. chicoute
, que nous avons importé d'un fichier csv, est un DataFrame
.
In [12]:
print("Type d'un tableau:", type(chicoute))
Nous verrons dans la prochaine section une manière plus fiable de sélectionner une Series
, mais notons pour cet exemple qu'une Series
peut être accédée sous la forme tableau.colonne
.
In [13]:
print("Type d'un tableau:", type(chicoute.CodeTourbiere))
On utiliser le terme sélectionner lorsque l'on désire choisir une ou plusieurs lignes et colonnes d'un tableau (la plupart du temps des colonnes). L'action de filtrer signifie de sélectionner des axes (la plupart du temps des lignes) selon certains critères.
Une matrice n'est pas contrainte à une structure tabulaire (lignes et colonnes). Isoler les parties désirées d'une matrice se fait dans numpy
comme pour une liste Python. Pour rappel, les éléments d'une liste sont sélectionnés par l'indice de leur position dans la liste à partir de 0, placé entre des crochets suivant le nom de l'objet.
In [14]:
ma_liste = ['premier', 'deuxième', 'troisième', 'quatrième']
ma_liste[1]
Out[14]:
Dans numpy
, on utilise aussi les crochets. Étant donné qu'une matrice est une liste de liste, les indices retenus sont ordonnés dans les crochets selon l'axe de la matrice (premier axe: lignes, deuxième axe: colonnes, troisième axe: profondeur, etc.). Les axes où l'on désire sélectionner tous les élément sont notés par un :
.
In [15]:
ma_matrice = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
ma_matrice
Out[15]:
In [16]:
ma_matrice[0, 1]
Out[16]:
In [17]:
ma_matrice[0, :]
Out[17]:
In [18]:
ma_matrice[[0,2], 1]
Out[18]:
numpy
est approprié pour effectué des sélections et des filtres. Néanmoins, les possibilités offertes par pandas
en font un outil souvent plus approprié pour effectuer ces opérations. Avec pandas
, certaines approches sont implicites, d'autres explicites.
Il y a deux manières implicites de sélectionner une colonne. La première est d'inscrire le nom du tableau, un point, puis le nom de la colonne désirée. Cette manière de fonctionner entraînent plusieurs inconvénients: impossible d'appeler plusieurs colonnes, erreurs si le nom de la colonne contient des caractères spéciaux, conflits avec les fonctions. Bien que plusieurs travaillent avec cette notation, nous travaillerons ici avec la notation en crochets, [ ]
. Entre les crochets, il est possible de noter une les caractères correspondant à la colonne désirée. Si l'on désire appeler plusieurs colonnes, elles doivent être appelées sous forme d'une liste Python, elle-même délimitée par des crochets (d'où les doubles-crochets).
In [19]:
chicoute['CodeTourbiere'].head()
Out[19]:
In [20]:
chicoute[['CodeTourbiere', 'Ordre']].head()
Out[20]:
Il est possible de créer une liste de noms de colonnes, puis de la placer entre les crochets.
In [21]:
chicoute.columns[17:]
Out[21]:
In [22]:
fol_cols = chicoute.columns[17:] # noms des colonnes de concentration foliaire
In [23]:
chicoute[fol_cols].head()
Out[23]:
Que venons-nous de faire?
fol_cols
contenant le nom des colonnes.fol_cols
du tableau chicoute
On peut travailler en exclusion avec la méthode drop
, par exemple exclure la colonne Site.
In [24]:
chicoute.drop(fol_cols, axis=1).head()
Out[24]:
Nous verrons plus tard, en plus de détails, en quoi consiste l'argument axis
. Pour l'instant, retenons que plusieurs fonctions de pandas sont disponibles dans l'axe vertical, celui des lignes, (axis=0
) et dans l'axe horizontal, celui des colonnes (axis=1
).
Cette approche est implicite, parce que pandas
interprète que vous désirez sélectionner des colonnes. Les méthodes de localisation par nom loc
et de locatlisation par indice iloc
sont, quant à elles, explicites. Elles découlent de la notation de numpy
et sont valides pour les lignes et les colonnes. Alors que loc
demande des Ìndex
(des noms de colonne ou des noms de ligne), iloc
est uniquement basé sur la position des lignes et des colonnes.
In [25]:
chicoute.loc[1:4, fol_cols]
Out[25]:
La fonction loc
permet de sélectionner une suite de colonnes.
In [26]:
chicoute.loc[1:4, 'TotalRamet_nombre.m2':'TotalFemelle_nombre.m2']
Out[26]:
Dans les exemples précédents, 1:4
est l'indice des lignes, qui est ici différent de la position, dont l'accès se fait via iloc
. Alors que loc
inclue tous les noms de colonne et d'indice, iloc
fonctionne comme les sélections dans les listes et les matrices numpy
: en incluant la première position et en excluant la dernière.
In [27]:
chicoute.iloc[0:4, 17:]
Out[27]:
Effectuer une sélection sur un tableau comme pour une matrice, c'est-à-dire sans iloc
, retournera une erreur.
In [28]:
chicoute[0:4, 17:]
In [29]:
chicoute['CodeTourbiere'] == 'BEAU'
Out[29]:
Le filtre est appliqué sur le tableau en le passant entre les crochets: pandas
détecte automatiquement que vous désirez effectuer un fitlre sur les lignes. C'est pourquoi cette approche est implicite.
In [30]:
chicoute[chicoute['CodeTourbiere'] == 'BEAU']
Out[30]:
Toute opération booléenne est valide.
In [31]:
chicoute[chicoute['N_pourc'] > 2.8]
Out[31]:
Les conditions booléennes peuvent être combinées avec les opérateurs et, &
, et ou, |
. Pour rappel,
Opération | Résultat |
---|---|
Vrai et Vrai | Vrai |
Vrai et Faux | Faux |
Faux et Faux | Faux |
Vrai ou Vrai | Vrai |
Vrai ou Faux | Vrai |
Faux ou Faux | Faux |
Pour éviter les confusions liées aux préscéances d'opérations, pandas
demande que les conditions soient spécifiées entre parenthèses.
In [32]:
chicoute[(chicoute['Ca_pourc'] < 0.4) & (chicoute['CodeTourbiere'] == 'BEAU')]
Out[32]:
Pour spécifier quelques options de correspondances exactes, utilisez la méthode isin
(utilisable seulement pour les pandas.Series
).
In [33]:
chicoute[(chicoute['N_pourc'] > 2.8) | (chicoute['SousTraitement'].isin(['B', 'Cu']))]
Out[33]:
In [34]:
chicoute.loc[chicoute.Ordre == 'A', ['Site', 'CodeTourbiere', 'Latitude_m', 'Longitude_m']]
Out[34]:
Inspecter un tableau fait partie des premières étapes d'un flux de travail. La méthode dtypes
, pour data types permet d'obtenir le type de données pour chaque colonne. Une colonne dont on devrait s'attendre être un nombre réel (float64), mais importée sous le type object indique souvent une erreur dans le fichier csv, possiblement due à une cellule annotée, l'utilisation non systématique de la notation d'une cellule vide. Une colonne de type object peut apparaître sous forme d'int64, même si le ficheir csv ne contient pas d'erreur. C'est le cas, par exemple, d'un numéro de site, dont le chiffre ne constitue pas une métrique.
In [35]:
chicoute.dtypes
Out[35]:
Ajustons le type de la colonne Site
pour qu'elle prenne le type category
, qui restreint les valeurs d'une colonne à prendre une valeur parmi des niveaux prédéterminés.
In [68]:
chicoute['Site'] = chicoute['Site'].astype('category')
La méthode describre
présente un sommaire des données en calculant certaines statistiques.
In [69]:
chicoute.describe()
Out[69]:
Le sommaire ne présente que 24 des 33 colonnes. La raison est que l'argument include
de describe
prend par défaut la valeur ['float64']
, soit une liste ne spécifiant que les colonnes contenant des nombres réels. On pourait forcer le sommaire à inclure tous les types de données, mais puisque les statistiques associées aux types de données sont différentes, il est préférable de les segmenter.
In [71]:
chicoute.describe(include=['object', 'category'])
Out[71]:
Les dimensions du tableau sont données par le descriptif .shape
In [39]:
chicoute.shape
Out[39]:
Pourquoi certaines commandes ont des parenthèses, et d'autres pas? Les deux types de commandes découlent de l'instrance de l'objet, ici un DataFrame. Les commandes accompagnées de parenthèses appellent une action par une fonction. Celles qui n'en ont pas appelent un attribu, un élément descriptif.
Action. grenouille.saute()
Description. grenouille.couleur
pandas
vous permet d'être plus spécifique sur les statistiques désirées. Supposons que vous désirez effectuer des calculs sur les concentrations en nutriment retrouvées dans les tissus foliaires.
In [40]:
chicoute[fol_cols].mean(axis=0)
Out[40]:
Nous avons sélectionné les colonnes désirées dans le tableau chicoute
, demander la moyenne, à travers les lignes, l'axe vertical 0 (axis=0
).
De la même manière, nous pourrions calculer la somme des concentrations sur chaque ligne. Puisque ce sont des pourcentages, le reste des constituants foliaires occupent $100 - \Sigma ~ nutriments$. Cette valeur de remplissage peut être insérée comme une novuelle colonne.
In [41]:
chicoute['Remplissage_pourc'] = 100 - chicoute[fol_cols].sum(axis=1)
fol_cols_R = chicoute.columns[17:] # noms des colonnes de concentration foliaire, en ajoutant le remplissage
chicoute[fol_cols_R].mean(axis=0)
Out[41]:
Avec describe
ou des opérations plus spécifiques, vous obtenez des sommaires globaux. Si vous désirez plutôt stratifier vos sommaires selon des groupes, par exemple une moyenne par tourbières. Cette approche s'appelle l'aggrégation. La méthode groupby
permet d'effectuer des calculs par groupe et sous-groupes.
Par exemple, calculons la médiane des pourcentages en nutrients pour chaque tourbière. Pour ce faire, nous avons besoin de l'information de regroupement, CodeTourbiere
, ainsi que les données de concentration. L'objet fol_cols
est de type Index
, qui ne peut (à ma connaissance) être modifié à moins d'être préalablement transformé en liste Python.
In [42]:
loc_cols = list(fol_cols)
loc_cols.append('CodeTourbiere') # ou loc_cols = list(fol_cols) + ['CodeTourbiere']
loc_cols
Out[42]:
Puis lançons la commande en chaîne:
loc
pour isoler les colonnes dont nous avons besoin, groupby
pour regrouper les données selon la colonne spécifiéemedian
pour l'opération à effectuer sur ces groupes
In [43]:
chicoute.loc[:, loc_cols].groupby('CodeTourbiere').median()
Out[43]:
Les groupes peuvent être classés en sous-groupes. De plus, une manière élégante de travailler avec les opérations en chaîne sur pandas
est de segmenter les commandes en plusieurs lignes, avec la barre oblique inversée \
pour signaler que la ligne suivante est la suite de la ligne courrante.
In [44]:
chicoute.loc[:, list(fol_cols) + ['CodeTourbiere', 'Ordre']].\
groupby(['CodeTourbiere', 'Ordre']).\
count()
Out[44]:
La méthode à utiliser est .sort_values()
. Les arguments principaux que prennent cette méthode sont la colonne ou les colonnes sur lequelles baser le tri (by=
) ainsi que l'ordre du tri (ascending=
). Il est possible de spécifier un tri par colonne, mais le tri par ligne, avec l'argument par défaut axis=0
, est généralement utilisé. La commande suivante effectue un tri descendant sur le rendement, puis ne conserve que les 10 premières lignes.
In [45]:
chicoute.sort_values(by='Rendement_g.5m2', ascending=False).head(10)
Out[45]:
apply
Si vous êtes familiers avec Excel, vous avez probablement déjà effectué des calculs par ligne ou par colonne autres que des sommes ou des moyennes. Les méthodes df.sum(axis=0)
ou df.mean(axis=1)
sont des cas particuliers de la méthode apply
, dont la fonction à exécuter est spécifiée dans l'argument func
.
In [46]:
chicoute.loc[:, ['N_pourc', 'P_pourc', 'K_pourc']].mean(axis=0)
Out[46]:
In [47]:
chicoute.loc[:, ['N_pourc', 'P_pourc', 'K_pourc']].apply(func=np.mean, axis=0)
Out[47]:
Pourquoi utiliser apply
? Pour appliquer des fonctions plus complexe. Ces fonctions peuvent être créées avec def
, ou spéficiées en un seule ligne avec lambda
. Par exemple, si vous cherchez à calculer la distance à vol d'oiseau entre chacune des mesures et une station météo située aux coordonnées [5702453, 490640], vous pourrez définir une fonction de Pythagore.
In [48]:
def dist_station(observation, station=[5702453, 490640]):
return np.sqrt((observation[0]-station[0])**2 + (observation[1]-station[1])**2)
In [49]:
chicoute.loc[0:5, ['Latitude_m', 'Longitude_m']].apply(func=dist_station, axis=1)
Out[49]:
Ou, plus simplement, définir une fonction lambda
.
In [50]:
chicoute.loc[0:5, ['Latitude_m', 'Longitude_m']].\
apply(func=lambda x: np.sqrt((x[0]-5702453)**2 + (x[1]-490640)**2),
axis=1)
Out[50]:
Des opération de type apply peuvent aussi être effectuées sur des matrices avec numpy
, en utulisant la fonction numpy.apply_along_axis
.
In [51]:
coord = chicoute.loc[0:5, ['Latitude_m', 'Longitude_m']].as_matrix()
coord
Out[51]:
In [52]:
np.apply_along_axis(func1d=lambda x: np.sqrt((x[0]-5702453)**2 + (x[1]-490640)**2), axis=1, arr=coord)
Out[52]:
In [53]:
experience = pd.DataFrame({'Site': ['Sainte-Zéphirine', 'Sainte-Aurélie', 'Saint-Serge-Étienne'],
'Traitement A': [4.1, 5.8, 2.9], 'Traitement B': [8.2, 5.9, 3.4],
'Traitement C': [6.8, np.NaN, 4.6]})
experience
Out[53]:
Pour transformer ce tableau en mode tidy (organisé), utilisons la méthode .merge()
. Cette méthde prend, comme argument id_var
, les colonnes qui ne doivent pas être fusionnées. Dans ce cas-ci, les sites.
In [54]:
experience_tidy = experience.melt(id_vars='Site')
experience_tidy
Out[54]:
La méthode .rename()
permet de renommer les indices et les colonnes. Dans notre cas, nous allons renommer les colonnes variable
et value
. J'en profite pour introduire l'argument inplace
, qui peut être évoqué pour de nombreuses méthodes dans pandas
. Spécifier inplace=True
signifie que les changements doivent être apportés au tableau. Par défaut inplace=False
, de sorte que tableau.methode(..., inplace=True)
est équivalent à tableau = tableau.methode(...)
.
In [55]:
experience_tidy.rename(columns={'variable': 'Traitement', 'value':'Rendement'}, inplace=True)
experience_tidy
Out[55]:
L'opération inverse avec la méthode .pivot()
.
In [56]:
experience_tidy.pivot(index='Site', columns='Traitement', values='Rendement')
Out[56]:
Des opérations de formatage plus complexes sont présentés dans la documentation de pandas
.
In [57]:
tourbieres = pd.read_csv('data/tourbieres.csv', sep=';')
tourbieres
Out[57]:
Notre information est organisée en deux tableaux, liés par la colonne CodeTourbiere
. Comment fusionner l'information pour qu'elle puisse être utilisée dans son ensemble? La fonction pandas.merge
effectue cette opération typique avec les bases de données.
In [58]:
chicoute_merge = pd.merge(left=chicoute, right=tourbieres, on='CodeTourbiere')
chicoute_merge.loc[1:5, list(chicoute.columns[0:10]) + list(tourbieres.columns)]
Out[58]:
Notez que j'ai voulu restreindre le nombre de colonne à afficher. Je m'y suis pris en combinant des noms de colonne en les additionnant sous forme de listes.
Nous pourrions procéder de la même manière pour fusionner des données climatiques. Le tableau chicoute
ne possède pas d'indicateurs climatiques, mais il est possible de les soutirer de stations météos placées près des site. Ces données ne sont pas disponibles pour le tableau de la chicouté, alors j'utiliserai des données fictives pour l'exemple.
Voici ce qui pourrait être fait.
Ces opérations demandent habituellement du tâtonnement. Il serait surprenant que même une personne expérimentée soit en mesure de compiler ces opérations sans obtenir de message d'erreur, et retravailler jusqu'à obtenir le résultat souhaité. L'objectif de cette section est de vous présenté un flux de travail que vous pourriez être amenés à effectuer et de fournir quelques éléments nouveau pour mener à bien une opération. Il peut être frustant de ne pas saisir toutes les opérations: passez à travers cette section sans jugement. Si vous devez vous frotter à problème semblable, vous saurez que vous trouverez dans ce manuel une recette intéressante.
In [59]:
stations = pd.DataFrame({'Station': ['A', 'B', 'C'], 'Latitude_m': [5702453, 5701870, 5696421], 'Longitude_m': [490640, 484870, 485929], 't_moy_C': [13.8, 18.2, 16.3], 'prec_tot_mm': [687, 714, 732]})
stations
Out[59]:
In [60]:
def eucldist(A, B):
dist = np.sqrt((A[0] - B[0])**2 + (A[1] - B[1])**2)
return(dist)
La fonction eucldist
demande deux arguments. A
correspondra au point du site, tandis que B
correspondra au point de la station. Dans la fonction, les arguments sont décortiqués en [0] et [1]. Il faut donc que A et B soient des listes ou des vecteurs numpy
. La méthode apply
décortiquera les lignes du tableau principal de cette manière. Toutefois, il faudra s'assurer que les stations soient aussi des vecteurs. Après tâtonnement, on y arrivera ainsi.
In [61]:
stationA = stations.loc[stations.Station == 'A', ['Latitude_m', 'Longitude_m']].values[0, :]
stationB = stations.loc[stations.Station == 'B', ['Latitude_m', 'Longitude_m']].values[0, :]
stationC = stations.loc[stations.Station == 'C', ['Latitude_m', 'Longitude_m']].values[0, :]
Créons un nouveau tableau qui hébergera les données des distances entre chaque point. Ces distances sont calculés grâce à la méthode apply
.
In [62]:
distances_stations = pd.DataFrame()
distances_stations['distA'] = chicoute.loc[:, ['Latitude_m', 'Longitude_m']].\
apply(lambda x: eucldist(A=x, B=stationA), axis=1)
distances_stations['distB'] = chicoute.loc[:, ['Latitude_m', 'Longitude_m']].\
apply(lambda x: eucldist(A=x, B=stationB), axis=1)
distances_stations['distC'] = chicoute.loc[:, ['Latitude_m', 'Longitude_m']].\
apply(lambda x: eucldist(A=x, B=stationC), axis=1)
In [63]:
chicoute['Station'] = np.nan
distance_minimum = distances_stations.min(axis=1)
Les lignes du tableau de distance où la colonne distA
est égale à la distance minimum sont gardée, et on sélectionn la colonne vide. Ces cellules correspondent aux endroits où la station A est la plus proche et prendrons la valeur A
. Idem our B
et C
.
In [64]:
chicoute.loc[distances_stations.distA == distance_minimum, 'Station'] = 'A'
chicoute.loc[distances_stations.distB == distance_minimum, 'Station'] = 'B'
chicoute.loc[distances_stations.distC == distance_minimum, 'Station'] = 'C'
Nous obtenons une Series
incluant les stations les plus proches, dont nous soutirons le vecteur avec la méthode .values
.
In [65]:
chicoute.Station.values
Out[65]:
In [66]:
chicoute.merge(stations[['Station', 't_moy_C', 'prec_tot_mm']], on='Station')
Out[66]:
pandas-profiling
La fonction describe
fournit plusieurs renseignements utiles pour apprécier un tableau. pandas-profiling
va un pas plus loin en fournissant un rapport détaillé d'un tableau. Pour l'installer, conda install pandas-profiling
.
In [67]:
import pandas_profiling as pdp
%matplotlib inline
pdp.ProfileReport(chicoute)
Out[67]:
Commandes usuelles.
df.head(5)
. Afficher les 5 premières lignes du tableau.df.tail(5)
. Afficher les 5 dernières lignes du tableau.df.describe()
. Afficher les statistiques sommaires du tableau.pd.read_csv()
. Importer un tableau.pd.to_csv()
. Exporter un tableauRéférences
Python for Data Analysis (McKinney, 2016). Wickham, H. (2014) https://www.jstatsoft.org/index.php/jss/article/view/v059i10/v59i10.pdf
Parent L.E., Parent, S.É., Herbert-Gentile, V., Naess, K. et Lapointe, L. 2013. Mineral Balance Plasticity of Cloudberry (Rubus chamaemorus) in Quebec-Labrador Bogs. American Journal of Plant Sciences, 4, 1508-1520. DOI: 10.4236/ajps.2013.47183