Python est un langage de programmation développé depuis les années 1990, qui connaît une popularité croissante dans le domaine de la data science ces dernières années.
Tandis que R est un langage spécifique pour les statistiques (Domain Specific Language), Python est un langage généraliste utilisé autant en data science que pour du développement web ou d'applications. En pratique, cela signifie que certains concepts utiles en data science ne sont pas présents directement dans le langage (data frames, valeurs manquantes, calcul matriciel, ...), mais cela est compensé par différents packages :
Grâce à ces packages, Python est notamment particulièrement utilisé pour le machine learning (en particulier le deep learning).
Le caractère généraliste de Python permet de faire facilement des ponts en domaines (data science, data engineering, développement web, etc.) et de profiter d'outils communs puissants.
Sur le plan technique, Python est un langage interprété (comme R) et non compilé (C++, Java). Cela fait perdre en performances, mais permet de développer de manière plus interactive. De même il utilise un typage dynamique (c'est-à-dire que ce n'est pas le développeur qui spécifie le type des variables) pour un développement plus flexible.
Pendant de nombreuses années, les développeurs étaient partagés entre les versions 2 et 3 de Python. En effet, Python 3 a introduit des modifications importantes parfois incompatibles avec Python 2 (par exemple, en Python 3, on utilise print('Bonjour')
alors que Python 2 utilisait print 'Bonjour'
. De nombreux développeurs ont donc été réticents à franchir le pas, malgré les améliorations apportées par les dernières versions. Cette situation a duré plusieurs années, mais depuis début 2020 Python 2 n'a officiellement plus de support. Il n'y a donc plus d'excuse pour utiliser cette version obsolète.
À la base, Python est un langage de programmation. Il est donc possible de l'utiliser dans un éditeur traditionnel comme Visual Studio Code, PyCharm...
L'usage de Python pour la data science nécessite cependant une certaine agilité car beaucoup d'études exploratoires sont plus axées sur la rapidité d'obtention des premiers résultats que sur la scalabilité ou la robustesse. Aussi, Jupyter propose un environnement simple dans lequel on peut programmer, organiser et visualiser les résultats rapidement, comme les notebooks RStudio. C'est l'outil le plus couramment utilisé en data science, et notamment c'est celui qui est mis à disposition sur le serveur de développement de l'équipe
Aide
-> Visite de l'interface utilisateur
Aide
-> Raccourcis clavier
markdown
et code
Maj+Entr
.Contrairement à RStudio, vous ne pouvez pas exécuter une seule ligne à la fois. Mais n'hésitez pas à créer des cellules d'une seule ligne si nécessaire (avec les raccourcis B
et A
par exemple) puis les supprimer (avec X
) ou les merger (avec M
)
print('Bonjour à tous')
Les opérateurs sont identiques en R et en Python...
... a part l'assignation <-
qui devient =
.
Python est un langage conçu pour être simple et compréhensible. Les opérations logiques sont donc simplement écrites en anglais :
!x
en R devient not x
en pythonx | y
en R devientx or y
en pythonx & y
en R devient x and y
en pythonStockez les valeurs 2 et 5 dans deux variables nommée respectivement a et b.
a = 2
b = 5
Effectuez quelques tests sur ces variables :
a == b
a < b
a <= b and b < 6
Dans la plupart des langages, les blocs sont délimités par des accolades, et l'utilisateur peut utiliser des espaces et des tabulations pour indenter son code à sa guise. Cela permet de faire un code fonctionnel et/ou lisible. Python a un parti pris original : il considère qu'on bon code doit être lisible et qu'on peut donc donner un sens à l'indentation. Les espaces et tabulations en début de ligne font donc partie de la syntaxe et permettent de déterminer le niveau d'un bloc, et un code mal indenté ne fonctionnera pas.
a = 3
Boucle if + condition
if a > 3:
print("a est plus grand que 3")
print('Ce texte est dans le if')
print('Ce texte est hors du if')
Else
if a > 3:
print("a est plus grand que 3")
else :
print("a est plus petit que 3")
Elif
if a > 3:
print("a est plus grand que 3")
elif a == 3:
print("a est égal à 3")
elif a >= 3:
print("a est supérieur ou égal à 3")
else :
print("a est plus petit que 3")
Boucle while
a = 0
while a < 5:
print(a)
a += 1
Boucle for
La fonction range(n)
donne les entiers inférieurs à n, en partant de 0.
for i in range(5):
print(i)
En lui donnant deux arguments, on peut aussi spécifier le début.
for i in range(2, 5):
print(i)
Break et continue
break
sort de la boucle.
for i in range(5):
if i == 3:
break
print(i)
print("The end")
continue
passe à l'itération suivante.
for i in range(5):
if i == 3:
continue
print(i)
print("The end")
for n in range(200, 601):
if (n % 7 == 0) and (n**3 % 50 == 0):
print(n)
# Correction 1
somme = 0
i = 0
while somme < 50:
if i%2 == 0:
somme += i
print(somme)
i+= 1
# correction 2
somme = 0
i = 0
for i in range(0, 100, 2):
somme += i
print(somme)
if somme > 50:
break
Écrivons une fonction qui permet de calculer à la racine carré d’un nombre. On l’appellera sqrt()
def sqrt(nombre):
nombre = nombre ** (1/2)
return nombre
sqrt(9)
Les fonctions peuvent utiliser des arguments optionnels
def puissance(nombre, exposant=2):
nombre = nombre ** (exposant)
return nombre
print(puissance(3))
print(puissance(9, exposant = 1/2))
print(puissance(3, 3))
Une fonction peut retourner plusieurs valeurs
def puissance(nombre):
return nombre, nombre**2, nombre**3
x, y, z = puissance(3)
print(x)
print(y)
print(z)
Exercice
Créer une fonction calcul_prix_ttc
qui prend en entrée un prix HT et le type de produit (luxe ou normal par défaut) et qui donne en sortie le prix TTC, le prix HT et la TVA. Si le produit est un produit de luxe TVA à 33% sinon TVA à 20%
def calcul_prix_ttc(prix_ht, type_tva='normal'):
"""
Retourne le prix TTC, le prix HT et la TVA à partir du type de TVA et du prix HT.
"""
if type_tva == 'luxe':
TVA = 0.33
else : TVA = 0.2
prix_ttc = prix_ht + prix_ht * TVA
return (prix_ttc, prix_ht, prix_ht * TVA)
calcul_prix_ttc(20, 'luxe')
Comme dans tous les langages de programmation, il existe différents types de variables en python : int
, float
, str
...
Python est un langage typé dynamiquement. Cela signifie que dans 90% des cas, vous n'avez pas à vous soucier du type de données que vous manipulez, du moment que vous savez ce que vous faites. Pour les 10 % restants, voici quelques astuces :
type
pour connaître le type d'une variable.isinstance
pour vérifier le type d'une variablefloat
, int
, str
pour convertir vos variablestype(0)
type('0')
str(0) == '0'
isinstance('0', int)
entier = 3
reel = 1.54
mot = 'mot'
reel
est bien un floatisinstance(reel, float)
entier
en string
str(entier)
Un itérable est un objet séquentiel que l'on peut parcourir élément par élément à l'aide d'une boucle for :
for toto in itérable:
... toto ...
Les listes, les tuples, les ensembles, les strings et les dictionnaires sont des itérables.
### Principe
Les listes python permettent de stocker une série d'éléments qui peuvent être de type différent. Prenons un exemple de liste de chaînes de charactères :
[“Bonjour”, “Je”, “Christophe”, “m’appelle”]
Exercices
Christophe
for
Christophe = ["Bonjour", "Je", "Christophe", "m'appelle"]
for mot in Christophe:
print(mot)
maliste[i]
Renvoie le i-ème élément de la liste. Attention, l'indexation des listes commence à 0 en Python !
maliste[-n]
Slicing négatif : nième élément en partant de la fin.
len(maliste)
Renvoie le nombre d'éléments de la liste
maliste[ début : fin ]
Slicing classique
On peut omettre un élément, il prend alors comme valeur par défaut :
Ainsi
maliste[2:]
prend toute la liste sauf les deux premiers éléments, tandis quemaliste[:-1]
enlève le dernier élément.
maliste[ début : fin : s]
Slicing par pas : Tous les s éléments.
maliste.append(x)
Ajoute un élément à la fin de la liste ; équivalent à
a[len(a)] = [x]
.
maliste.insert(x, i)
Insère
x
à l'indicei
et décale les éléments suivants
maliste.pop(i)
Enlève l'élément à l'indice
i
et avance les éléments suivants
maliste1 + maliste2
Concaténation de
maliste1
etmaliste2
Cela signifie que
[1, 2, 3] + [4, 5, 6]
ne fait pas la somme des éléments mais donne[1, 2, 3, 4, 5, 6]
. Python ne fait pas de calcul matriciel avec les listes !
n * maliste
Répète n fois
maliste
. Par exemple :2 * [0,1] = [0, 1, 0, 1]
element in maliste
Le mot-clé
in
permet de tester si une valeur se trouve dans une liste (ou de manière générale, un itérable).
Exercice n°1
Voici une liste contenant toutes les lettres de l'alphabet
alphabet = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
alphabet[:5]
alphabet[-3:]
alphabet[:5:2]
alphabet[::-1]
é
est-elle dans la liste ?"é" in alphabet
Exercice n°2
Christophe = ["Bonjour", "Je", "Christophe", "m'appelle"]
On a un problème avec cette phrase, deux mots sont inversés. Trouvez un moyen d’inverser le dernier item avec l’avant dernier, en utilisant pop
.
Christophe = ["Bonjour", "Je", "Christophe", "m'appelle"]
Christophe.append(Christophe.pop(2))
print(Christophe)
Attardons nous maintenant sur le concept de référence. En effet, une liste python ne contient pas des éléments mais des références (adresses mémoires) vers ces éléments. C'est ce qui rend leur usage si flexible : vous pouvez avoir une liste qui contient des entiers, des strings, et même des instances de classe.
Cette organisation cause souvent, chez le pythoniste débutant, un trouble existentiel. En effet :
# Ma 1ère liste contient 1, 2, 3, 4
list1 = [1, 2, 3, 4]
# Je définis ma 2ème liste comme égale à la première
list2 = list1
# Je modifie ma première liste
list1[0] = 10
print(list2)
print('Fichtre, la liste 2 a été modifiée aussi...')
# Et maintenant si je modifie la liste 2
list2[-1] = -10
print(list1)
print('Diantre, la liste 1 a été modifiée aussi...')
Lorsque vous executez list2 = list1
, vous stockez dans la variable list2
les références vers les objets de list1
. De fait, lorsque vous modifiez les objets eux-mêmes, les références de list1
et de list2
pointent toutes les deux vers les nouveaux objets.
Pour pallier ce problème, on peut utiliser la fonction copy()
https://docs.python.org/3/library/copy.html
list1 = [1, 2, 3, 4]
list2 = list1.copy()
list1[0] = 10
list2[-1] = -10
print('list1', list1)
print('list2', list2)
list2 = [n*2 for n in list1]
list2
contient chaque élément delist1
multiplié par 2.
La compréhension de liste est une expression qui permet de construire une liste à partir de tout autre type itérable (liste, tuple, chaîne de caractères…) en une ligne. Le résultat obtenu est toujours une liste.
[ n**2 for n in range(1,11) ]
sum
sum( [ n**2 for n in range(1,11) ] )
Les compréhensions de liste peuvent aussi impliquer une condition if
[entier for entier in range(20) if entier%2==0]
sum([n for n in range(20) if n%2==1])
[n for n in range(20) if n%5==0 or n%7==0]
[n for n in range(20) if (n%5==0 or n%7==0) and n%15!=0]
En Python, les strings sont manipulables comme des listes de charactères.
S = 'cordel'
S
On peut notamment accéder aux caractères individuellement :
S[0]
for lettre in S:
print(lettre)
machaine.split(c)
Sépare
machaine
en utilisant le (ou les) caractèresc
comme séparateur.
c.join(maliste)
Rassemble
maliste
en utilisant la chaînec
comme séparateur
machaine.lower()
Met tous les charactères de
machaine
en minuscule.
machaine.replace(a, b)
Remplace les caractères données par
a
par ceux données parb
Exercices
nombreOcurrences(x, mot)
qui prend en argument un caractère x
et une chaîne de caractère mot
et qui renvoie le nombre de fois où le caractère x
est présent dans mot
. Par exemple, si mot
est 'java'
, nombreOcurrences(’a’, mot)
vaut 2def nombreOcurrences(x, mot):
return sum([c == x for c in mot])
nombreOcurrences('a', 'java')
premierMot(chaine)
qui renvoie le premier mot d’une chaîne de caractère. Par exemple si ma chaîne est "samedi soir, je vais au cinéma"
, on renverra "samedi"
.def premierMot(chaine):
return chaine.split(' ')[0]
premierMot("samedi soir, je vais au cinéma")
\\
par des /
dans la chaîne suivante :chemin = "C:\\Users\\Quentin\\Documents\\Perso\\Ne pas ouvrir\\francois.jpg"
chemin.replace('\\', '/')
D = {
clé1 : valeur1,
clé2 : valeur2,
}
Les dictionnaires sont des objets pouvant en contenir d'autres, à l'instar des listes. Cependant, au lieu d'héberger des informations dans un ordre précis, ils associent chaque objet contenu à une clé (la plupart du temps, une chaîne de caractères). Par exemple, un dictionnaire peut contenir un carnet d'adresses et on accède à chaque contact en précisant son nom.
('Batman','Robin')
('Harley Quinn','Poison Ivy')
('Iron man','War machine')
('Phenix','Cyclope')
dico_duo = {
'Batman':'Robin',
'Harley Quinn':'Poison Ivy',
'Iron man':'War machine',
'Phenix':'Cyclope'
}
dico_duo
On peut accéder aux éléments en précisant la clé entre crochets :
dico_duo['Batman']
'Phenix'
en utilisant les []
dico_duo['Phenix']
'Phenix'
par 'Jean Grey'
dico_duo['Phenix'] = 'Jean Grey'
dico_duo
'Ant man'
et 'The Wasp'
au dictionnairedico_duo['Ant man'] = 'The Wasp'
dico_duo
k in dict
Vérifie si la clé
k
est présente dans le dictionnaire
for k in dict:
Lorsqu'on itère sur un dictionnaire, on incrémente la clé. Pour obtenir la valeur, il suffit de faire
dict[k]
dans la boucle.
for k in dico_duo:
print(k, 'and', dico_duo[k])
dict.items()
La fonction
items()
renvoie les couples (clé, valeur) du dictionnaire. Il est possible d'itérer dessus de la manière suivante :for k,v in dict.items()
for k, v in dico_duo.items():
print(k, 'and', v)
dict.values()
La fonction
values()
renvoie les valeurs du dictionnaire.
d = {'nom': 'Dupuis', 'prenom': 'Jacques', 'age': 30}
d
30 in d.values()
for k, v in d.items():
if isinstance(v, str):
print(k)
La compréhension de dictionnaire est similaire à la compréhension de liste, à la différence près que les []
sont remplacés par des {}
et que l'expression doit comporter <clé>:<valeur>
. Cela permet donc de créer un dictionnaire rapidement.
{str(i):i for i in [1,2,3,4,5]}
fruits = ['apple', 'mango', 'banana','cherry']
{f:len(f) for f in fruits}
Les tuples sont des listes immutables, c'est-à-dire que leurs valeurs ne peuvent pas être modifiées. Leur définition se fait avec ()
et leur manipulation est similaire à celle des listes.
ducks = ('riri', 'fifi', 'loulou')
for d in ducks:
print(d)
ducks[1]
try:
ducks[2] == 'titi'
except:
print('Ça marche pas !')
Les ensembles (sets) sont des séquences non ordonnées et non indexables de valeurs distinctes. Elles se définissent grâce aux {}
. Elles sont très pratiques pour calculer des intersections ou des unions d'éléments distincts uniques.
fruits1 = {"apple", "banana", "cherry"}
fruits2 = {"orange", "pear","apple"}
print(fruits1)
print(fruits2)
fruits1.intersection(fruits2)
fruits1.union(fruits2)
Ils peuvent aussi permettre de supprimer les doublons des listes (en perdant l'ordre)
l = ['toto', 'tata', 'titi', 'titi', 'tutu']
list(set(l))
En suivant la logique de python, on parcourt un itérable avec la syntaxe suivante :
for élément in itérable:
... élément ...
Seulement il arrive que l'on ait besoin de l'indice de l'élément courant dans la boucle. Pour ca, il existe la fonction enumerate
:
for indice, élément in enumerate(itérable):
... indice ...
... élément ...
colors = ["red", "green", "blue", "purple"]
for i, c in enumerate(colors):
print(i, c)
Il arrive parfois que l'on souhaite itérer sur deux listes en même temps. Pour éviter de passer par une boucle sur un indice (comme en Matlab par exemple), Python intègre la fonction zip
qui permet de "coller" deux listes :
for élément_a, élément_b in zip(list_a, list_b):
... élément_a ...
... élément_b ...
number_list = [3, 2, 1]
str_list = ['three', 'two', 'one']
for n, s in zip(number_list, str_list):
print(n, s)
Exemples d'itérables :
A = [1, 2, 3, 4, 5]
B = range(5)
A et B contiennent les mêmes données. Mais la grosse différence, c'est que dans la liste A, tous les éléments sont déjà calculés et stockés en mémoire. Tandis que pour l'objet B, le calcul est effectué à la volée pour chaque itération.
C'est le principe du générateur : une fonction qui utilise le mot-clef yield
au lieu de return
et qui n'est exécuté que lorsqu'elle est itérée dans une boucle for
.
def mon_generateur():
i = 40
while i <= 56:
i += 2
yield i
Par exemple, voici un générateur infini pour la suite de Fibonacci :
Fib[0] = 0
Fib[1] = 1
Fib[n+2] = Fib[n] + Fib[n+1]
def fibonacci():
f1 = 0
yield f1
f2 = 1
yield f2
while True:
f3 = f1 + f2
yield f3
f1, f2 = f2, f3
for i, fib in enumerate(fibonacci()):
print(i, fib)
# On l'arrête avant l'infini
if i == 20:
break
On peut appliquer une fonction sur tous les éléments d'un itérable à l'aide de la fonction map
def double(x):
return 2*x
l = [1, 2, 3]
l2 = map(double, l)
l2
Le résultat retourné est un générateur. On peut le parcourir :
l2 = map(double, l)
for x in l2:
print(x)
On peut aussi le convertir directement en liste :
l2 = map(double, l)
list(l2)
Si la fonction à appliquer n'est pas trop complexe, on peut la définir à la volée à l'aide des expressions lambda
, qui sont des fonctions anonymes.
list(map(lambda x: 2*x, [1, 2, 3]))
Pour des fonctions simples une compréhension de liste est néanmoins plus lisible :
[2*x for x in [1, 2, 3]]
filter
permet de garder les éléments en fonction d'un critère booléen
def is_impair(x):
return x%2 == 1
list(filter(is_impair, range(10)))
Réécrire cette compréhension de liste avec map et filter :
[i**2 for i in range(10) if i%2 ==1]
list(map(lambda x: x**2, filter(lambda x: x%2==1, range(10))))
Python dispose de nombreux packages pour faire des tâches plus spécialisées.
Par exemple numpy
est un package spécialisé dans le calcul numérique, permettant de manipuler des tableaux et de faire du calcul matriciel.
# Installation de numpy
!pip install numpy
Il existe différentes façons de charger un package :
import numpy
permet de charger tout le package. Ainsi la fonction array
de numpy
pourra être appellée sous la forme numpy.array
import numpy as np
permet de faire la même chose mais en changeant le nom à utiliser : np.array
. C'est une façon très fréquente de faire pour certains packages dont l'abréviation est courante (np
pour numpy
, pd
pour pandas
, etc.). Évitez d'inventer vos propres abrévations !from numpy import array
permet de ne charger que la fonction array
et de l'appeler directement. Dans la mesure où cela fait perdre de vue l'origine de la fonction et où cela peut créer des conflits entre fonctions ayant le même nom mais venant de différents packages, c'est à utiliser avec parcimonie !import numpy as np
Numpy est la librairie de calcul numérique de Python. Elle permet d'effectuer efficacement des opérations sur des vecteurs ou des matrices, appelées "arrays". Ces tableaux se manipulent de la même manière que les listes de base : A[0:3]
renvoie les 4 premières valeurs du vecteur A
. Mais nous verrons leur intérêt pour le calcul matriciel.
On peut créer un vecteur (array 1D) à partir de la liste des valeurs
a = np.array([1, 2, 3])
a
Pour faire un array 2D, on passe une liste de listes.
b = np.array([[1, 2, 3], [4, 5, 6]])
b
On peut récupérer les dimensions du tableau grâce à shape
b.shape
On peut aussi faire des tableaux 3D voire plus
c = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
c
c.shape
La fonction reshape
permet de changer la forme de l'array, tant qu'on garde le même nombre d'éléments. Elle prend comme argument le tuple des dimensions souhaités (ou un entier pour un tableau 1D).
np.reshape(b, (6, 1))
np.reshape(b, 6)
On peut aussi utiliser la valeur -1
pour laisser numpy déterminer la taille adéquate
np.reshape(b, (3, -1))
np.reshape(b, -1)
Certains tableaux classiques peuvent être construits directement, en fournissant le tuple des dimensions souhaités
np.zeros((3, 3))
np.ones(4)
np.eye(4)
np.random.random((3, 3))
np.random.randint(1, 6, 10)
L'indexation peut se faire comme pour les listes sous la forme d'un entier ou d'un intervalle (début:fin:pas)
a
a[0]
a[-2:]
Cette syntaxe s'étend pour les tableaux multidimensionnels
b
b[0, 1]
b[0, 1:]
b[:, 1:]
On peut aussi utiliser l'indexation booléenne, en lui passant un tableau de True/False
is_impair = a%2==1
print(is_impair)
a[is_impair]
Le principal apport des tableaux numpy par rapport aux listes Python et leur facilité d'utilisation pour le calcul matriciel. Les principales opérations peuvent se faire par élément
x = np.array([1, 2, 3])
y = np.array([4, 5, 6])
x + y
b*2
La multiplication de tableaux 2D se fait élément par élément. La multiplication matricielle peut se faire par la fonction dot
b*b
b.T
np.dot(b, b.T)
numpy
propose aussi différentes fonctions mathématiques
np.median(a)
np.std(a) # écart-type
np.sum(b)
np.sum(b, axis=0) # somme sur les colonnes
np.sum(b, axis=1) # somme sur les lignes
En Python, il est facile de gérer des erreurs grâce à try
.
La manière simple de l'utiliser est :
except:
dico = {'a': 1, 'b': 2}
try:
print(dico['c'])
except:
print("Clé absente mais c'est pas grave")
Si on ne veut rien faire en cas d'erreur et continuer comme si de rien n'était, on peut utiliser pass
try:
code_qui_plante
print('Oups')
except:
pass