Chapitre 1 : Manipuler les données
Numpy : bibliothèque python de bas niveau utilisée pour le calcul scientifique :
- Permet notamment de travailler avec des tableaux et matrices multidimensionnels et volumineux homogènes (c’est-à-dire de même type).
- Dont l’objet principal est le ndarray (un type de tableau à N dimensions)
Pandas : package de manipulation de données pour manipuler des données de haut niveau construits sur numpy
- La série est le principal élément constitutif des pandas. Une série est un tableau unidimensionnel basé sur numpy ndarray. Dans un dataframe, une série correspond à une colonne.
- Un dataframe est un tableau de données étiquetée en 2 dimensions dont les colonnes sont constituées par un ndarray, une série ou un autre dataframe.
Numpy
Numpy est le package incontournable pour effectuer du calcul scientifique en python, en facilitant notamment la gestion des tableaux et des matrices de grande dimension. La documentation officielle est disponible via ce lien.
Numpy permet de manipuler des arrays ou des matrices, pouvant être par exemple construites à partir d’arrays. Un array correspond à un tableau de valeurs du même type. Les opérations mathématiques sont facilitées par un ensemble de fonctions accessibles dans le package numpy. Le site offre un large panorama des fonctionnalités de numpy.
NB : L’alias np est très souvent utilisé pour désigner numpy
Petit rappel : en python, les indices commencent à zéro. Le premier élément de l’array s’obtient donc avec la commande a[0]
App 1 : Charger numpy
import numpy as np
App 2 : Créer un array constitué que de 1 et un autre que de 0
np.ones(10)
np.zeros(10)
App 3 : Créer un array avec des éléments parcourant 0 à 10 inclus de 2 en 2
np.arange(0,11,2)
App 4 : Concaténer l’array précédent avec un array contenant les entiers de 0 à 5 inclus
# Solution 1
np.concatenate([np.arange(0,11,2),np.arange(6)])
# Solution 2
np.hstack([np.arange(0,11,2),np.arange(6)])
App 5 : Concaténer verticalement (pour former un array de listes) l’array précédent avec un array contenant les entiers de 0 à 5 inclus
# Solution 1
np.vstack([np.arange(0,11,2),np.arange(6)])
# Solution 2 : si la concaténation horizontale des deux arrays est déjà affectée à una variable, il peut être intéressant de simplement la reformater
x=np.hstack([np.arange(0,11,2),np.arange(6)])
x.reshape(2,6)
App 6 : Vérifier que tous les éléments de l’array contenant les entiers de 0 à 5 inclus sont inférieurs à 10
np.all(np.arange(6)<10)
App 7 : Vérifier que l’un des éléments de l’array contenant les entiers de 0 à 5 inclus est supérieur strictement à 4
np.any(np.arange(6)>4)
Pandas
Le package pandas fournit de nombreux outils pour faciliter la manipulation des données. Pandas permet notamment de :
- importer et exporter des données depuis divers formats (csv, Excel, **, HTML, json, parquet …)
- définir des dataframes cad des tableaux de données variables (colonnes) x individus (lignes)
- manipuler des dataframes (gestion des valeurs manquantes, création de variables, filtre selon les variables ou les individus, fusion de différents dataframes)
- effectuer des statistiques descriptives (comptage et agrégation de données par groupe)
- tracer directement des graphes grâce à l’intégration dans pandas d’objets matplotlib
La documentation officielle est disponible via ce lien. Le site fournit aussi un large éventail d’exemples.
App 1 : Charger pandas
import pandas as pd
App 2 : Lire les données de population du fichier Excel base-pop-historiques-1876-2015.xls et afficher les 4 premières lignes
pop=pd.read_excel("base-pop-historiques-1876-2015.xls", sheet_name="pop_1876_2015", header=5)
pop.head(4)
NB : Même s’il reste préférable d’opter pour un autre format que celui de SAS, pandas offre toutefois la possibilité de gérer le format sas7bdat avec la fonction read_sas. Voici un exemple de code qui utilise cette fonction :
import pandas as pd
data = pd.read_sas("train.sas7bdat", format="sas7bdat", encoding='utf8')
data.head(2)
App 3 : Afficher les dimensions de la table pop
pop.shape
App 4 : Afficher les nom de colonnes de la table pop
pop.columns
App 5 : Lire les données de population du fichier csv commune2019.csv et afficher les 2 premières lignes
communes=pd.read_csv("commune2019.csv", sep=",", header=0)
communes.head(2)
App 6 : Compter le nombre de valeurs na et non na pour la variable “comparent”
communes.comparent.isna().value_counts()
App 7 : Afficher la fréquence de chaque modalité de la variable “typecom”
communes.typecom.value_counts()
App 8 : Afficher le type des variables de la table communes
communes.dtypes
# Pour obtenir des informations plus détaillées
communes.info()
App 9 : Si aucun typage n’a été imposé dans le read_csv, on constate que les régions (reg) sont considérées comme float alors que les départements (dep) sont considérés comme un objet. Pourquoi la variable reg n’est pas perçue comme un entier ? Pourquoi la variable dep est interprétée comme un objet ?
La variable relative à la région (reg) est considérée comme un float et non comme un entier car elle contient des valeurs manquantes. La variable relative au département (dep) est interprétée comme un objet python car elle contient des caractères de type string en raison de la présence de la Corse (“2A” et “2B”).
NB : A quoi correspond le type object ?
Le type Objet de python est le type de base qui s’appuie sur la classe parente de toutes les classes.
App 10 : Afficher les observations relatives à la ville de Lyon
# Solution 1 :
communes[communes.libelle=='Lyon']
# Solution 2 (surtout utile si on souhaite chercher plusieurs libellés) :
communes[communes.libelle.isin(['Lyon'])]
App 11 : Etes vous sûrs d’afficher toutes les observations associées à la ville de Lyon ? Afficher les observations contenant “yon”
# Première solution avec la fonction find
libelle_yon=communes[communes.libelle.str.find('yon')!=-1].libelle
print(libelle_yon)
# Deuxième solution avec la fonction contains
libelle_yon_regex=communes[communes.libelle.str.contains('yon')]
print(libelle_yon_regex)
App 12 : Afficher maintenant les observations contenant ‘lyon’ en gérant la casse
libelle_lyon=communes[communes.libelle.str.lower().str.find('lyon')!=-1].libelle
App 13 : Afficher maintenant les libellés présents dans les deux listes (ceux contenant lyon et ceux contenant yon)
# Solution 1
set(libelle_lyon) & set(libelle_yon)
# Solution 2
set(libelle_lyon).intersection(libelle_yon)
App 14 : Afficher maintenant les libellés de la liste associée à yon et pas à la liste associée à lyon
set(libelle_yon).difference(libelle_lyon)
App 15 : Importer l’onglet “définitif_patrimoine” du fichier Excel isfcom2017
isf=pd.read_excel("isfcom2017.xlsx", sheet_name="définitif_patrimoine", header=1)
print(isf.shape)
isf.head(2)
App 16 : Afficher le nom des colonnes de ce dataframe
isf.columns
App 17 : Fusionner la table des communes avec cette nouvelle table en ne conservant que les observations communes
Si vous avez tenté de fusionner les tables sans nettoyer la variable relative au code Insee dans le dataframe isf, vous avez du constater que peu d’observations ont matché. On constate que le code Insee dans isf contient des espaces qu’il faut préalablement supprimer.
isf["codeCommune"]=isf["Code commune (INSEE)"].str.replace(" ", "")
# Plutôt privilégier (car cela permet de se prémunir des cas de double espaces)
import re
isf["codeCommune"]=[re.sub(' ','', str(x)) for x in isf["Code commune (INSEE)"]]
Les expressions régulières permettent de repérer des schémas ou des ensembles de séquences de caractères semblables dans une chaîne de caractères. Le package re regroupe un ensemble d’opérations réalisables à partir d’expressions réguières. Ce tutoriel permet par exemple de s’exercer à leur utilisation. Certes ? Mais commment puis-je être vérifier que j’ai écrit la bonne expression régulière ? Vous pouvez notamment tester les expressions régulières avec ce site.
A partir de là, vous pouvez procéder à la fusion !
isf_communes=pd.merge(communes, isf, left_on="com", right_on="codeCommune", how='inner')
print(isf_communes.shape)
isf_communes.head(2)
Vous auriez pu directement nommer le code Insee nettoyé avec le même nom que celui présent dans la table communes (cad com). Dans ce cas, il suffit de directement indiquer on="com” dans la fonction merge.
App 18 : Pourquoi le nombre de lignes de isf_communes est supérieur à celui de isf
On peut notamment constater que des villes sont présentes plusieurs fois dans la table communes.
isf_communes[isf_communes.libelle.isin(list(isf_communes.libelle.value_counts().index[isf_communes.libelle.value_counts()==2]))]
App 19 : Supprimer les observations associées aux communes déléguées (typecom égal à COMD)
# On vérifie au préalable la répartition de la variable *typecom*
isf_communes.typecom.value_counts()
isf_communes=isf_communes[isf_communes.typecom!="COMD"]
isf_communes.shape
App 20 : Vérifier que la ville issue de chaque table est identique dans la base fusionnée
# Solution 1 : utiliser l'opérateur &
probleme=isf_communes[(isf_communes.ncc.str.lower()!=isf_communes.Commune.str.lower().str.replace('-'," ")) & \
(isf_communes.libelle.str.lower()!=isf_communes.Commune.str.lower().str.replace('-'," "))]
probleme.head(4)
# Solution 2 : utiliser numpy (np.all)
probleme=isf_communes[np.all([isf_communes.ncc.str.lower()!=isf_communes.Commune.str.lower().str.replace('-'," "),
isf_communes.libelle.str.lower()!=isf_communes.Commune.str.lower().str.replace('-'," ")], axis=0)]
probleme.head(4)
Regardons la répartition des observations posant problème selon la variable typecom
probleme.typecom.value_counts()
Les observations relatives aux arrondissements sont correctes, la précision de l’arrodissement dans l’un des libellés et non dans l’autre entraîne mécaniquement la détection de ces observations.
On peut alors visualiser les observations restantes. Des problèmes d’accentuation sont à l’orgine des différeces entre les libellés issus des deux tables.
probleme[probleme.typecom=="COM"][['ncc', 'libelle', 'Commune', 'Région', 'Départements']]
App 21 : Afficher, par ordre décroissant, le nombre total de redevables par région
isf_communes.groupby('Région')['nombre de redevables'].sum().sort_values(ascending=False)
App 22 : Afficher, par ordre décroissant, le nombre moyen de redevables par département pour les départements où ce nombre moyen dépasse 500
nb_isf_dep=isf_communes.groupby('Région', as_index=False)['nombre de redevables'].sum().sort_values(by='nombre de redevables', ascending=False)
nb_isf_dep[nb_isf_dep['nombre de redevables']>500]
Le as_index=False permet de ne pas passer la variable du by en index (les libellés des colonnes utilisées pour le regroupement sont conservées au sein d’un dataframe). Au delà du caractère esthétique, ce paramètre qui assure que le résultat du group_by reste un dataframe est particulièrement utile lorsque le dataframe final souhaite être réploité en python. Dans certains cas, on retrouve aussi la fonction reset_index.
App 23 : Afficher le nombre moyen de redevables par région x département
nb_moyen_regDep=isf_communes.groupby(['Région', 'Départements'], as_index=False)['nombre de redevables'].mean()
nb_moyen_regDep.head(4)
App 24 : Afficher le nombre moyen de redevables par région x département x typecom en créant une colonne par typecom
nb_moyen_regDepTypeCom=pd.crosstab(index=[isf_communes['Région'], isf_communes['Départements']], columns=isf_communes.typecom, values =isf_communes['nombre de redevables'], aggfunc =np.mean).reset_index()
nb_moyen_regDepTypeCom.head(4)
App 25 : Transposer la table isf_communes pour que les trois variables ‘nombre de redevables’,‘patrimoine moyen en €’,‘impôt moyen en €’ soient au sein d’une même variable en tant que modalité et les valeurs associées dans une seule colonne
isf_transpose=isf_communes.melt(id_vars=list(set(isf_communes.columns).difference(set(['nombre de redevables', 'patrimoine moyen en €', 'impôt moyen en €']))))
isf_transpose.head(2)
Pour reformater les données, il peut être utile de recourir aux fonctions pivot, stack ou unstack. Pour cela, vous pouvez vous reporter à l'aide fournie sur le site officiel de pandas.