Résumé: Ce calepin propose une rapide présentation de Pyhton, en insistant sur l'exécution de commandes interactives ou de scripts avec un IDE (integrated Development Environment) et l'utilisation d'un calepin. On présente brièvement les types et structures élémentaires de données, les structures de contrôle, les fonctions, classes et modules. Une rapide description des librairies scientifiques: Numpy, Matplotlib, Scipy
est également proposée. La présentation et la rédaction de calepin sont très largement inspirées de ceux disponibles sur le site de .
Ce calepin introduit le langage libre Python et décrit les premières commandes nécessaires au pré-traitement des données avant l'utilisation de méthodes statistiques avec ce langage. Pour une présentation plus détaillée, de très nombreuses ressources pédagogiques sont disponbiles dont le tutoriel officiel de Python 3.4., les sites pythontutor.com
, courspython.com
, le livre de Sheppard (2014) qui présentent une introduction à Python pour l'Econométrie et la Statistique et celui de Mac Kinney (2013), principal auteur de la bibliothèque pandas
.
Python et ses librairies peuvent être installés dans quasiment tout environnement matériel et système d'exploitation à partir du site officiel. Voici les principales librairies scientifiques définissant des structures de données et fonctions de calcul indispensables.
ipython
: pour une utilisation interactive de Python, numpy
: pour utiliser vecteurs et tableaux, scipy
: intègre les principaux algorithmes numériques, matplotlib
: pour les graphes, pandas
: structure de données et feuilles de calcul, patsy
: formules statistiques,statsmodels
: modélisation statistique,seaborn
: visualisation de données,scikit-learn
: algorithmes d'apprentissage statistique.Néanmoins, compte tenu de la complexité de l'opération, il est plus simple de faire appel à une procédure d'installation intégrant les principales librairies. Dans cette UE, on propose de privilégier l'utilisation d'Anaconda développé par l'entreprise commerciale Continuum Analytics (mais libre de droits pour une utilisation académique) avec le choix de la version 3.4 de Python. Conda
est l'utilitaire (commande en ligne) qui permet les mises à jour et installations des librairies complémentaires.
Dans cette UE, on choisit d'utiliser le langage Python pour exécuter des programmes ou scripts à l'aide d'un interprète de commande (IDLE
) de manière interactive. En situation pédagogique, c'est l'utilisation et la réalisation d'un notebook Jupyter (calepin) qui est privilégiée à partir d'un simple navigateur .
Les commandes sont regroupées dans des cellules suivies de leur résultat après exécution. Ces résultats et commentaires sont stockés dans un fichier spécifique .ipynb
et sauvegardés. Les commandes LaTeX sont acceptées pour intégrer des formules, la mise en page est assurée par des balises HTML ou Markdown.
La commande de sauvegarde permet également d'extraire les seules commandes Python dans un fichier d'extension .py
. C'est une façon simple et efficace de conserver tout l'historique d'une analyse pour en faire une présentation ou créer un tutoriel. Le calepin peut être en effet chargé sous un autre format: page html
, fichier .pdf
ou diaporama.
Le projet Jupyter propose cet environnement de calepin pour beaucoup de langages (Pyhton, Julia, Scala...) dont R. Il devient un outil indispensable pour assurer simplement la reproductibilité des analyses.
L'ouverture d'un navigateur sur un calepin Jupyter est obtenu, selon l'installation, à partir des menus ou en exécutant:
jupyter notebook
dans une fenêtre de commande.
Une fois le calepin ouvert,
.ipynb
.html
pour une page web ou une verion .pdf
.py
regroupant les commandes python pour une version opérationnelle.Spyder
¶Pour la réalisation d'applications et programmes plus complexes, l'usage d'un IDE libre comme Spyder est recommandé. Ce dernier est intégré à la distribution Anaconda
et sa présentation proche de celles de Matlab ou RStudio.
Comme pour RStudio, Spider
ouvre plusieurs fenêtres:
#%%
.Entrer les commandes ci-dessous dans le calepin et les exécuter cellule par cellule en cliquant sur le bouton d'exécution de la cellule courante. Sauvergarder le calepin sous les différents formats .ipynb
, .html
, .pdf
et .py
import sys
print(sys.version)
# Début d'une session Python gérée à l'aide d'un calepin.
# Le script est divisé en cellules avec généralement l'affichage d'au plus un résultat par cellule.
## Importer les librairies nécessaires
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from pylab import *
import os
## Commande pour obtenir les graphiques dans le calepin
%matplotlib inline
#%% Créer un data frame avec pandas
data = pd.DataFrame({
'Gender': ['f', 'f', 'm', 'f', 'm', 'm', 'f', 'm', 'f', 'm'],
'TV': [3.4, 3.5, 2.6, 4.7, 4.1, 4.0, 5.1, 4.0, 3.7, 2.1]})
data
# Génération de variables aléatoires gaussiennes
xx = randn(100,100)
y = mean(xx,0)
# Graphique
plot(y)
show()
help(randn)
La déclaration des variables est implicite et la syntaxe est très proche de celle de R (toutefois il n'y a pas de type factor
).
a=3 # est un entier
b=1. # est un flottant
# Comparaison
a==b
# Affichage et type des variables
type(a)
# Chaîne de caractère
a='bonjour '
b='le '
c='monde'
a+b+c
Les listes permettent des combinaisons de types. Attention, le premier élément d'une liste ou d'un tableau est indicé par 0, pas par 1.
# Exemples de listes
liste_A = [0,3,2,'hi']
liste_B = [0,3,2,4,5,6,1]
liste_C = [0,3,2,'hi',[1,2,3]]
# Accès au deuxième d'une liste
liste_A[1]
# Accès au dernier élément
liste_C[-1]
# Accès au premier élément du dernier élément
liste_C[-1][0]
liste_C[-2]
liste_B[0:2] # Sous-liste
liste_B[0:5:2] # début:fin:pas
# Fonctions de listes
List=[3,2,4,1]
List.sort()
print(List)
List.append('hi')
print(List)
List.extend([7,8,9])
print(List)
List.append([10,11,12])
print(List)
Un dictionnaire est similaire à une liste mais chaque entrée est assignée par une clé / un nom, il est défini avec des accolades. Cet objet est utilisé pour la construction de l'index des colonnes (variables) du type DataFrame de la librairie pandas
.
months = {'Jan':31 , 'Fev': 28, 'Mar':31}
months['Jan']
months.keys()
months.values()
months.items()
# si alors sinon
a=1
if a>0:
b=0
print(b)
else:
b=-1
print(b)
for i in range(1,8,2):
print(i)
# Définition d'une fonction
def pythagorus(x,y):
""" Calcule l'hypotenuse d'un triangle """
r = pow(x**2+y**2,0.5)
return x,y,r
# Exemple d'appel
pythagorus(x=3,y=4)
# Aide
help(pythagorus)
Voici quelques librairies indispensables au calcul scientifique
NumPy
¶Cette librairie définit le type de données array
ainsi que les fonctions de calcul qui y sont associées. Il contient aussi quelques fonctions d'algèbre linéaire et statistiques. Il n'est finalement utilisé que pour la définition du type array
car les fonctions numériques sont beaucoup plus développées dans SciPy
.
Matplotlib
¶Celle-ci propose des fonctions de visualisation / graphs avec des commandes proches de celles de Matlab. Aussi connue sous le nom de pylab
. La gallerie de cette librairie propose tout un ensemble d'exemples de graphiques avec le code Python pour les générer.
# Importation
import numpy as np
from pylab import *
gaussian = lambda x: np.exp(-(0.5-x)**2/1.5)
x=np.arange(-2,2.5,0.01)
y=gaussian(x)
plot(x,y)
xlabel("x values")
ylabel("y values")
title("Gaussian function")
show()
C'est de loin la structure de données la plus utilisée pour le calcul scientifique sous Python. Elle décrit des tableaux ou matrices multi-indices de dimension $ allant de 1 à 40. Tous les éléments doivent être de même type (booléen, entier, réel, complexe).
Les tableaux ou tables de données (data frame), bases d'analyses statistiques et regroupant des objets de types différents sont décrits avec la librairie pandas
.
array
¶# Importation
import numpy as np
my_1D_array = np.array([4,3,2])
print(my_1D_array)
my_2D_array = np.array([[1,0,0],[0,2,0],[0,0,3]])
print(my_2D_array)
my_2D_array[1]
array
¶np.arange(5)
np.ones(3)
np.ones((3,4))
np.eye(3)
np.linspace(3, 7, 10)
D=np.diag([1,2,4,3])
print(D)
print(np.diag(D))
Le module numpy.random
fournit toute une liste de fonctions pour la génération de matrices aléatoires.
from numpy import random
random.rand(4,2) #tirage uniforme
random.randn(4,2) #tirage selon la loi N(0,1)
v=random.randn(1000)
import matplotlib.pyplot as plt
h=plt.hist(v,20) # histogramme à 20 pas
show()
A=random.randn(64,64)
plt.imshow(A,interpolation="nearest")
plt.colorbar
plt.show()
# Sauver, écrire dans un fichier, lire un fichier
M=random.randn(10,10)
np.savetxt('data.csv',M,fmt='%2.2f',delimiter=',')
# Au format propre à numpy : npy
np.save('data.npy',M)
np.load('data.npy')
v=np.array([1,2,3,4,5])
print(v)
v[1:4]
v[1:4:2]
v[::]
v[ : :2] # par pas de 2
v[ : 3]
v[3 :] # à partir de l'indice 3
v[-1] # dernier élément
v[-2 :] # deux derniers éléments
M=random.rand(4,3)
print(M)
ind=[1,2]
M[ind] # lignes d'indices 1 et 2
M[:,ind] # colonnes d'indices 1 et 2
M[[0,2],[1,2]] # renvoie M[0,1] et M[2,2]
M[np.ix_([0,2],[1,2])]
(M>0.5)
M[M>0.5]
a=np.array([[0,1],[2,3],[4,5]])
np.ndim(a) # Nombre de dimensions)
np.size(a) # Nombre d’éléments
np.shape(a) # Tuple contenant la dimension de a
np.transpose(a) # Transposée
a.T # autre façon de définir la transposée
a.min(), np.min(a) # Valeur min
a.sum(), np.sum(a) # Somme des valeurs
a.sum(axis=0) # Somme sur les colonnes
a.sum(axis=1) # sur les lignes
Quelques manipulations:
np.r_[1:4,10,11] # Concaténation en ligne
np.c_[1:4,11:14] # Concaténation en colonne
np.arange(6).reshape(3,2)
A=np.array([[1,2],[3,4]])
np.tile(A,(3,2)) # Répétition de la matrice A
A=np.array([[1,2],[3,4]])
B=np.array([[11,12],[13,14]])
#Concaténation en ligne
np.concatenate((A,B),axis=0)
# Equivalent à
np.vstack((A,B))
# Concaténation en colonne
np.concatenate((A,B),axis=1)
# Equivalent à
np.hstack((A,B))
array
s¶# somme
a=np.arange(6).reshape(3,2)
b=np.arange(3,9).reshape(3,2)
c=np.transpose(b)
a+b
a*b # produit terme à terme
np.dot(a,c) # produit matriciel
np.power(a,2)
np.power(2,a)
a/3
# Importation
import numpy as np
from scipy import linalg
A = np.array([[1,2],[3,4]])
linalg.inv(A)
linalg.det(A)
la,v = linalg.eig(A)
l1,l2 = la
# valeurs propres
print(l1, l2)
# 1er vecteur propre
print(v[:,0])
# SVD de A
U,s,V = linalg.svd(A)
print(s**2)
linalg.eig(np.dot(np.transpose(A),A))
On présente maintenant l'utilisation de Python pour la préparation de données qui peuvent tenir en mémoire une fois réorganisées. Cette partie est une initiation aux fonctionnalités de la librairie pandas
et à la classe DataFrame
.
La richesse des fonctionnalités de la librairie pandas
est la raison principale d'utiliser Python pour extraire, préparer (et éventuellement analyser) des données. En voici un bref aperçu.
Series
et DataFrame
ou table de données..csv
, format fixe, compressés), binaires (HDF5 avec Pytable
), HTML, XML, JSON, MongoDB, SQL... Cette partie est basée sur la documentation en ligne qui inclut également des tutoriels à exécuter pour compléter et approfondir les possibilités de traitement de données avec la librairie pandas
.
A titre d'illustration, on utilise des données issues d'une compétition du site Kaggle: Titanic: Machine learnic from Disaster. Le concours est terminé mais les données sont toujours disponibles sur le site avec des tutoriels utilisant Excel, Python ou R.
Une des raisons du drame, qui provoqua la mort de 1502 personnes sur les 2224 passagers et membres d'équipage, fut le manque de canots de sauvetage. Il apparaît que les chances de survie dépendaient de différents facteurs (sexe, âge, classe...). Le but du concours est de construire un modèle de prévision (classification supervisée) de survie en fonction de ces facteurs. Les données sont composées d'un échantillon d'apprentissage (891) et d'un échantillon test (418) chacun décrit par 11 variables dont la première indiquant la survie ou non lors du naufrage.
Series
et DataFrame
¶De même que la librairie Numpy
introduit le type array
indispensable à la manipulation de matrices en calcul scientifique, celle pandas
introduit les classes Series
(séries chronologiques) et DataFrame
ou table de données indispensables en statistique.
La classe Series
est l'association de deux arrays
unidimensionnels. Le premier est un ensemble de valeurs indexées par le 2ème qui est souvent une série temporelle. Ce type est introduit principalement pour des applications en Econométrie et Finance où Python est largement utilisé.
La classe DataFrame
est proche de celle du même nom dans le langage R, il s'agit d'associer avec le même index de lignes des colonnes ou variables de types différents (entier, réel, booléen, caractère). C'est un tableau bi-dimensionnel avec des index de lignes et de colonnes mais il peut également être vu comme une liste de Series
partageant le même index. L'index de colonne (noms des variables) est un objet de type dict
(dictionnaire). C'est la classe qui sera principalement utilisée dans ce tutoriel.
# Exemple de data frame
import pandas as pd
data = {"state": ["Ohio", "Ohio", "Ohio",
"Nevada", "Nevada"],
"year": [2000, 2001, 2002, 2001, 2002],
"pop": [1.5, 1.7, 3.6, 2.4, 2.9]}
frame = pd.DataFrame(data)
# ordre des colonnes
pd.DataFrame(data, columns=["year", "state", "pop"])
# index des lignes et valeurs manquantes (NaN)
frame2=pd.DataFrame(data, columns=["year", "state", "pop", "debt"],
index=["one", "two", "three", "four", "five"])
# liste des colonnes
frame.columns
# valeurs d'une colonnes
frame["state"]
frame.year
# "imputation"
frame2["debt"] = 16.5
frame2
# créer une variable
frame2["eastern"] = frame2.state == "Ohio"
frame2
frame2.columns
# supprimer une variable
del frame2[u"eastern"]
frame2.columns
Les données du naufrage du Titanic illustrent l'utilisation de pandas
.
# Importations
import pandas as pd
import numpy as np
# Chargement des données
path='./'
df = pd.read_csv(path+'titanic-train.csv',nrows=5)
df
df.tail()
# tout lire
df = pd.read_csv(path+"titanic-train.csv")
df.head()
# Des variables sont inexploitables
# Choisir les colonnes utiles
df=pd.read_csv(path+"titanic-train.csv",
usecols=[1,2,4,5,6,7,9,11],nrows=5)
df.head()
La librairie pandas
, inclut un type category
assez proche de celui factor
de R. Il devrait normalement être déclaré dans un dictionnaire au moment par exemple de la lecture (dtype={"Surv":pd.Categorical...}
) mais ce n'est pas le cas, c'est donc le type objet
qui est déclaré puis modifié. Il est vivement recommandé de bien affecter les bons types à chaque variable ne serait-ce que pour éviter de faire des opérations douteuses, par exemple arithmétiques sur des codes de modalités.
df=pd.read_csv(path+"titanic-train.csv",skiprows=1,header=None,usecols=[1,2,4,5,9,11],
names=["Surv","Classe","Genre","Age","Prix","Port"],dtype={"Surv":object,
"Classe":object,"Genre":object,"Port":object})
df.head()
df.dtypes
Redéfinition des bons types.
df["Surv"]=pd.Categorical(df["Surv"],ordered=False)
df["Classe"]=pd.Categorical(df["Classe"],ordered=False)
df["Genre"]=pd.Categorical(df["Genre"],ordered=False)
df["Port"]=pd.Categorical(df["Port"],ordered=False)
df.dtypes
Pour la discrétisation d'une variable quantitative. Il est d'un bon usage de définir les bornes des classes à des quantiles, plutôt qu'également espacées, afin de construire des classes d'effectifs sensiblement égaux. Ceci est obtenu par la fonction qcut
. La fonction cut
propose par défaut des bornes équi-réparties à moins de fournir une liste de ces bornes.
df["AgeQ"]=pd.qcut(df.Age,3,labels=["Ag1","Ag2",
"Ag3"])
df["PrixQ"]=pd.qcut(df.Prix,3,labels=["Pr1","Pr2",
"Pr3"])
df["PrixQ"].describe()
Le recodage des variables qualitatives ou renommage en clair des modalités est obtenu simplement.
df["Surv"]=df["Surv"].cat.rename_categories(
["Vnon","Voui"])
df["Classe"]=df["Classe"].cat.rename_categories(
["Cl1","Cl2","Cl3"])
df["Genre"]=df["Genre"].cat.rename_categories(
["Gfem","Gmas"])
df["Port"]=df["Port"].cat.rename_categories(
["Pc","Pq","Ps"])
df.head()
Il est possible d'associer recodage et regroupement des modalités en définissant un dictionnaire de transformation.
data = pd.DataFrame({"food":["bacon","pulled pork",
"bacon", "Pastrami",
"corned beef", "Bacon", "pastrami", "honey ham",
"nova lox"],
"ounces": [4, 3, 12, 6, 7.5, 8, 3, 5, 6]})
data
meat_to_animal = {
"bacon": "pig",
"pulled pork": "pig",
"pastrami": "cow",
"corned beef": "cow",
"honey ham": "pig",
"nova lox": "salmon"
}
# Eviter les mélanges de majuscules minuscules
# en mettant tout en minuscule
data["animal"] = data["food"].map(
str.lower).map(meat_to_animal)
data
data["food"].map(lambda x: meat_to_animal[x.lower()])
dfs = pd.DataFrame({"key": ["b", "b", "a", "c",
"a", "b"],"data1": range(6)})
print(dfs)
pd.get_dummies(dfs["key"])
help(pd.get_dummies)
Générer des indicatrices des modalités ou dummy variables.
dummies = pd.get_dummies(dfs['key'], prefix='key')
df_with_dummy = dfs[['data1']].join(dummies)
df_with_dummy
Permutation aléatoire:
dfs = pd.DataFrame(np.arange(5 * 4).reshape(5, 4))
sampler = np.random.permutation(5)
sampler
dfs
dfs.take(sampler)
Tirage aléatoire avec remplacement ou bootstrap.
bag = np.array([5, 7, -1, 6, 4])
sampler = np.random.randint(0, len(bag), size=10)
draws = bag.take(sampler)
draws
Les opérations arithmétiques entre Series
et DataFrame
sont possibles au même titre qu'entre array
. Si les index ne correspondent pas, des valeurs manquantes (NAN) sont créées à moins d'utiliser des méthodes d'arithmétique flexible
(add, sub, div, mul
) autorisant la complétion par une valeur par défaut, généralement 0.
Une fonction quelconque (lambda
) peut être appliquée avec une même commande qu'apply
de R.
# la table de données
frame = pd.DataFrame(np.random.randn(4,3),
columns=list("bde"),
index=["Utah", "Ohio", "Texas", "Oregon"])
# une fonction
f = lambda x: x.max() - x.min()
frame.apply(f, axis=1)
Trier une table selon les valeurs d'une variable ou d'un index.
frame = pd.DataFrame(np.arange(8).reshape((2,4)),
index=["three", "one"],
columns=["d", "a", "b", "c"])
frame.sort_index()
frame.sort_index(axis=1)
frame.sort_index(axis=1, ascending=False)
frame.sort_values(by="b")
La commande rank
remplace les valeurs par leur rang dans l'ordre des lignes ou des colonnes.
frame = pd.DataFrame({"b": [4.3, 7, -3, 2],
"a": [0, 1, 0, 1],"c": [-2, 5, 8, -2.5]})
frame.rank(axis=1)
frame.rank(axis=0)
df.dtypes
df.describe()
df.head()
import matplotlib.pyplot as plt
%matplotlib inline
df["Age"].hist()
plt.show()
df["Age"].plot(kind="box")
plt.show()
df["Prix"].plot(kind="hist")
plt.show()
# qualitatif
df["Surv"].value_counts()
df["Classe"].value_counts()
df["Genre"].value_counts()
df["Port"].value_counts()
df.plot(kind="scatter",x="Age",y="Prix")
plt.show()
# afficher une sélection
df[df["Age"]>60][["Genre","Classe","Age","Surv"]]
df.boxplot(column="Age",by="Classe")
plt.show()
df.boxplot(column="Prix",by="Surv")
plt.show()
# table de contingence
table=pd.crosstab(df["Surv"],df["Classe"])
print(table)
# Mosaic plot
from statsmodels.graphics.mosaicplot import mosaic
mosaic(df,["Classe","Genre"])
plt.show()
mosaic(df,["Surv","Classe"])
plt.show()
La gestion des données manquantes est souvent un point délicat. De nombreuses stratégies ont été élaborées, et nous ne décrivons ici que les plus élémentaires à mettre en oeuvre avec pandas
.
Il est ainsi facile de supprimer toutes les observations présentant des données manquantes lorsque celles-ci sont peu nombreuses et majoritairement regroupées sur certaines lignes ou colonnes.
df = df.dropna(axis=0)
df = df.dropna(axis=1)
Pandas
permet également de faire le choix pour une variable qualitative de considérer `np.nan
comme une modalité spécifique ou d'ignorer l'observation correspondante.
Autres stratégies:
La variable âge contient de nombreuses données manquantes. La fonction fillna
présente plusieurs options d'imputation.
# Remplacement par la médiane d'une variable quantitative
df=df.fillna(df.median())
df.describe()
# par la modalité "médiane" de AgeQ
df.info()
df.AgeQ=df["AgeQ"].fillna("Ag2")
# par le port le plus fréquent
df["Port"].value_counts()
df.Port=df["Port"].fillna("Ps")
df.info()
Ces imputations sont pour le moins très rudimentaires et d'autres sont à privilégier pour des modélisations plus soignées...
# tables
df1 = pd.DataFrame({"key": ["b", "b", "a", "c",
"a","a", "b"],"data1": range(7)})
df2 = pd.DataFrame({"key": ["a", "b", "d"],
"data2": range(3)})
pd.merge(df1,df2,on="key")
La gestion des clefs manquantes est en option: entre autres, ne pas introduire de ligne (ci-dessus), insérer des valeurs manquantes ci-dessous.
# valeurs manquantes
pd.merge(df1,df2,on="key", how="outer")
Concaténation verticale (axis=0) ou horizontales (axis=1) de tables. La concaténation horizontale est similaire à la jointure (option outer
).
# tables
df1 = pd.DataFrame({"key": ["b", "b", "a", "c",
"a", "a", "b"],"var": range(7)})
df2 = pd.DataFrame({"key": ["a", "b", "d"],
"var": range(3)})
# concaténation verticales
pd.concat([df1,df2],axis=0)
# concaténation horizontale
pd.concat([df1,df2],axis=1)