Introduction Python¶
Pour des géographes, commencer Python par le biais des applications et outils comme QGIS et ArcGIS est très pertinent. Néanmoins, ces environnements de travail présentent des limites. C'est pourquoi, il semble pertinent de maitriser un environnement Python plus classique.
Ici nous allons présenter l'utilisation de Python dans l'environnement/distribution Anaconda : https://www.anaconda.com/download
Anaconda est un environnement (une distribution) reconnu pour faire du traitement de données avancé et permet notamment de coupler R et Python, ce qui parlera aux géographes/géomaticiens. Il a des défauts, notamment celui d'être relativement lourd. Il existe des alternatives à cet environnement/distribution comme : Miniconda, Pipenv, Mamba, Miniforge... La version de base d'Anaconda téléchargé ci-dessus est simple à installer et permet d'avoir accès à des bibliothèques classiques de Python déjà installées comme : Numpy, Scipy, Matplotlib, Pandas et NetworkX. De plus, le notebook et l'IDE proposé (Spyder) sont relativement simples d'utilisation.
Comment reproduire ce notebook¶
Pour reproduire ce notebook, il existe deux solutions. La première solution, celle par laquelle il faut commencer selon moi, consiste à utiliser Spyder. Une fois Spyder ouvert, celui-ci se présente en théorie comme dans l'image ci-dessous. Dans la partie de gauche, vous trouver un éditeur syntaxique où vous pourrez reproduire les séquences de codes dans l'ordre du notebook dans un même fichier python que vous pourrez sauvegarder régulièrement. Pour exécuter les lignes de code de l'éditeur, vous pouvez cliquer sur la flèche verte (F5) ou sélectionner tout ou partie du code et faire un clique droit, puis cliquer sur "Executer la sélection ou la ligne courante" (F9). Les éléments créés par ce code apparaitront en haut à droite : les objets créés seront accessibles par l'onglet "Explorateur de variables" (en double cliquant sur un objet vous pourrez consulter son contenu) ; les graphes ou les cartes s'afficheront dans l'onglet "Graphes". A noter que les "print()" s'afficheront dans la console en bas à droite. Cette console peut permettre d'exécuter directement du code, mais celui-ci ne sera pas sauvegardable. En théorie, comme dans QGIS, on utilisera la console uniquement pour tester du code et corriger/comprendre des erreurs.
La deuxième solution pour reproduire ce notebook consiste à utiliser Jupyter pour créer un notebook ou exécuter ce notebook. Cette solution ne sera pas présentée ici.
Les fondamentaux de Python¶
Exceptionnellement, cette partie introductive est conçue pour être exécutée directement depuis la console, car elle n'a pas vocation à être enregistrée dans un fichier python.
Dans la console de Spyder, vous pouvez tapez des opérations mathématiques, celle-ci vous renvoie les résultats.
2+3
5
2/3
0.6666666666666666
Néanmoins, il conviendra en programmation de créer des objets à l'aide de l'affectation. Cette affectation utilise un nom d'objet (commençant par une lettre), puis un signe =, puis la valeur à affecter.
a = 2
b = 3
La console ne renvoie rien, mais vous pouvez lui demander d'effectuer le calcul suivant :
a + b
5
Plus élégamment, vous pouvez stocker ce calcul dans un nouvel objet, puis demander à la console d'afficher le résultat à l'aide de la fonction print().
c = a + b
print(c)
5
Python a créé les objets demandés et leur a attribué un type : ici des entiers (int).
type(a)
int
Pour créer des décimaux, il faudra utiliser une écriture décimale. Pour passer de l'un à l'autre, on utilisera les fonctions float() et int().
d = 3.0
type(d)
int(d)
float(d)
3.0
Pour créer un objet texte (str) on utilise les ''. La fonction str() permet de transformer un objet numérique en texte, les fonctions float() et int() peuvent au contraire faire l'opération inverse.
e = 'Texte'
print(e)
e = str(a)
print(e)
e = float(a)
print(e)
Texte 2 2.0
Pour créer des objets qui contiennent plusieurs valeurs, il est possible de créer des listes en utilisant des [] et des , pour séparer les valeurs. Pour consulter les valeurs d'une liste on pourra utiliser les index toujours en utilisant les []. Les index Python commencent à 0. Pour modifier une valeur on pourra utiliser ces index et l'affectation. La fonction len() permet d'obtenir le nombre d'objets d'une liste. On peut rajouter une valeur à une liste avec un + par exemple ou avec la méthode append(). La méthode remove() permet de supprimer le premier élément dont la valeur est égale à x.
a = [2,3,4]
print(a)
print(type(a))
print(a[0])
a[2] = 10
print(a)
a = a + [7]
print(a)
a.append(9)
print(a)
a.remove(3)
print(a)
[2, 3, 4] <class 'list'> 2 [2, 3, 10] [2, 3, 10, 7] [2, 3, 10, 7, 9] [2, 10, 7, 9]
Les index peuvent être négatifs pour retouver des valeurs en partant de la fin de la liste. Des sous-listes peuvent être récupérées en utilisant ":" . Pour trier une liste par ordre croissant on pourra utiliser la méthode sort(), tandis que la méhode reverse permet d'inverser l'ordre. Attention, l'affectation d'une liste à partir d'une autre liste crée deux objets qui ont les mêmes valeurs. Pour copier deux listes, on utilisera plutôt la méthode copy().
print(a[-1])
print(a[1:3])
print(a[1:])
print(a[1:-1])
print(a.index(7))
a.sort()
print(a)
a.reverse()
print(a)
a = list(range(10))
print(list(a))
b = a
b[2]=100
print(a)
print(b)
9 [10, 7] [10, 7, 9] [10, 7] 2 [2, 7, 9, 10] [10, 9, 7, 2] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [0, 1, 100, 3, 4, 5, 6, 7, 8, 9] [0, 1, 100, 3, 4, 5, 6, 7, 8, 9]
Une liste n'est pas nécessairement structurée. Elle peut contenir tout type d'objets, notamment d'autres listes et ainsi constituer une liste de listes. Une liste de listes structurée correspond à ce que l'on peut considérer vulgairement comme un tableau. Les "lignes" de ce tableau seront constituées des différentes listes et seront facilement accessibles avec les index []. Les colonnes seront constituées des différentes valeurs des listes et plus difficilement accessibles. On pourra se servir d'un deuxième index [] pour accéder à une valeur précise. On pourra rajouter une nouvelle liste à une liste de listes avec un +.
a = [[2,3,4],[4,5,6]]
print(a)
print(a[0])
print(a[0][0])
a = a + [[2,6,7]]
print(a)
[[2, 3, 4], [4, 5, 6]] [2, 3, 4] 2 [[2, 3, 4], [4, 5, 6], [2, 6, 7]]
Les tuples sont un deuxième "type de liste". Les tuples fonctionnent presque comme des listes. Néanmoins, on ne peut pas modifier une valeur d'un tuple à l'aide d'une simple affectation (cela permet de se confronter au message d'erreur de Python que d'essayer). On peut créer des listes de tuples ou des tuples de listes... On utilise des parenthèses () pour créer des tuples.
a = (1,2,3)
print(a)
print(a[1])
a[0] = 3
(1, 2, 3) 2
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[12], line 4 2 print(a) 3 print(a[1]) ----> 4 a[0] = 3 TypeError: 'tuple' object does not support item assignment
Deux bibliothèques très utiles : Numpy et Matplotlib¶
Pour enrichir le langage de base de Python et ainsi faciliter son utilisation, des bibliothèques ont été développées. La première bibliothèque que l'on peut présenter est Numpy. En effet, pour effectuer des calculs numériques, Python mérite d'être enrichi. A titre d'exemple, on a vu précédemment que des listes de listes structurées pouvaient être considérées comme des tableaux, mais que les colonnes étaient peu accessibles, car ce ne sont pas des tableaux... Numpy propose un objet array qui comble ce vide. Il permet bien d'autres choses, comme par exemple des calculs de base.
Pour importer une bibliothèque, il suffit d'utiliser import et de préciser le nom de la bibliothèque. Si cela ne fonctionne pas c'est que la bibliothèque n'est pas téléchargée. Pour créer un array on pourra alors exécuter la séquence ci-dessous.
a = [[2,3,4],[5,6,7]]
import numpy
b = numpy.array(a)
print(b)
print(type(b))
print(b * 2) # Avec l'array Numpy, on multiplie par deux toutes les valeurs
print(a * 2) # Avec une liste, on double les inforamations de la liste
[[2 3 4] [5 6 7]] <class 'numpy.ndarray'> [[ 4 6 8] [10 12 14]] [[2, 3, 4], [5, 6, 7], [2, 3, 4], [5, 6, 7]]
Pour accéder à une fonction d'une bibliothèque, il faut donc en théorie écrire le nom de la bibliothèque suivi du nom de la fonction. C'est un peu long... C'est pourquoi il possible de charger les fonctions d'une bibliothèque avec un structure du type : from nomdelabibli import *
from numpy import *
c = array(a)
print(c)
[[2 3 4] [5 6 7]]
Néanmoins, cette solution est lourde, car ici on a besoin que d'une fonction et on charge toutes les fonctions Numpy. La troisième solution consiste à donner un petit surnom à la bibliothèque puis à utiliser ce surnom pour appeler les fonctions.
import numpy as np
d = np.array(a)
print(d)
[[2 3 4] [5 6 7]]
Une fois votre array créé, vous pouvez utiliser les index comme ci-dessous pour accéder à ce que vous voulez dans votre tableau. On utilise ici des croisillons # pour les commentaires.
print(b[0][0]) #Accéder à une valeur comme avec une liste
print(b[0,0]) #Une alternative plus rapide pour accéder à une valeur (ligne,colonne)
print(b[0,:]) #Accéder à toutes les valeurs d'une ligne
print(b[:,0]) #Accéder à toutes les valeurs d'une colonne
2 2 [2 3 4] [2 5]
Numpy permet surtout de faire des calculs statistiques de base.
print(np.sqrt(4)) #racine carrée
print(np.mean(a[0])) #moyenne
print(np.var(a[0])) #variance
print(np.corrcoef(a)) #matrice de corrélations
2.0 3.0 0.6666666666666666 [[1. 1.] [1. 1.]]
La deuxième bibliothèque présentée est celle de matplotlib. En effet, cette bibliothèque est une référence pour réaliser des graphiques dans Python. Plus particulièrement on utilisera la fonction plot() de pyplot. Pour effectuer, un graphique de points, il faut lui donner une liste de coordonnées x (valeurs en abscisse) et une liste de coordonnées y (valeur en ordonnée) et lui préciser la typologie de ces points. Vos graphes apparaissent dans Spyder au-dessus de la console dans l'onglet "Graphes".
import matplotlib.pyplot as plt
plt.plot(a[0],a[1],'o')
[<matplotlib.lines.Line2D at 0x174c70baf50>]
Pour relier ces points par des lignes, on peut plus simplement écrire cela :
plt.plot(a[0],a[1])
[<matplotlib.lines.Line2D at 0x174c6809090>]
Ou utiliser des styles de ligne :
plt.plot(a[0],a[1],'--')
[<matplotlib.lines.Line2D at 0x174c7256e50>]
Il possible de superposer deux plots, en partant par exemple de l'éditeur et en utilisant la fonction show(). On pourra utiliser des arguments à la fonction plot() pour préciser les couleurs, les tailles...
a = [[2,3,4],[5,6,7]]
import matplotlib.pyplot as plt
plt.plot(a[0],a[1], linestyle='dashed', color="grey", linewidth=1)
plt.plot(a[0],a[1], 'o', color="orange", markersize=12)
plt.show()
Les boucles et les tests¶
Les boucles et les tests sont deux éléments essentiels dans les langages de programmation. En Python, les boucles reposent sur un principe d'indentation. Ainsi, après avoir écrit les caractéristiques d'une boucle, on passe à la ligne suivante et on utilise une tabulation pour écrire les commandes que l'on souhaite exécuter dans une boucle.
Les boucles sont des structures qui permettent de répéter un bloc de code plusieurs fois, jusqu'à ce qu'une condition spécifique soit remplie. Elles sont utilisées pour automatiser des tâches répétitives, parcourir des collections de données (comme des listes ou des tableaux), et effectuer des opérations itératives. Ci-dessous, on va parcourir les éléments de la liste a.
a = [10, 15, 5]
for i in range(len(a)):
print(a[i])
10 15 5
Plutôt que d'utiliser les fonctions range() et len() qui permettent de créer une liste allant de 0 à 2 et d'utiliser chacune de ces valeurs pour modifier l'index de a, on peut directement itérer les éléments d'une liste comme ci-dessous :
for i in a:
print(i)
10 15 5
Il existe principalement deux types de boucles : les boucles « for » (elles sont utilisées lorsque le nombre d'itérations est connu à l'avance) et les boucles « while » (elles continuent à s'exécuter tant qu'une condition donnée est vraie). Pour utiliser ces dernières, il faut maitriser les tests.
Les tests en Python sont très simples à faire :
a[0] < a[1]
True
a[0] > a[1]
False
a[0] == a [1]
False
Les tests peuvent être utilisés, comme condition d'arrêt des boucles, en l'occurrence pour les boucles while().
i = 0
while a[i] < 12 :
print(a[i])
i = i + 1
10
Les tests peuvent être aussi placés dans des conditions si (if) qui permettent d'exécuter un bloc de code uniquement si une condition donnée est vraie. Elles sont essentielles pour prendre des décisions dans un programme et pour adapter son comportement en fonction de différentes situations.
cap = input("Ecrivez en capitale la capitale de la France : ")
if cap == "PARIS" :
print("Bravo !")
if cap != "PARIS" :
print("La capitale de la France est Paris")
Bravo !
Les boucles sont fondamentales pour écrire des programmes efficaces et concis, car elles évitent la duplication de code et permettent de traiter de grandes quantités de données de manière automatisée. Beaucoup d'algorithmes de résolution de problèmes (par exemple en mathématique) reposent aussi sur des principes itératifs. Ainsi, vous trouverez plein d'exemples de fonctions de calculs utilisant des boucles (comme par exemple, pour faire la somme d’une série de données, trouver si un nombre est premier…). Néanmoins, très souvent on trouvera des bibliothèques qui auront codé ces algorithmes de résolution mathématique/statistique, ce qui explique pourquoi un logiciel comme R (qui est un logiciel de statistique) permet à ces utilisateurs d’utiliser peu les boucles dans leurs codes. Ici, pour illustrer l'intérêt des boucles, nous allons nous appuyer sur le jeu de données utilisé dans le notebook suivant.
arcs = [(0,43), (0,56), (1,2), (1,6), (2,5), (2,14), (3,4), (3,17),(4,5), (4,16), (5,15), (5,14), (6,7), (6,14), (7,8), (7,14), (8,9), (8,11), (10,11), (10,24), (11,12), (11,24), (11,25), (12,13), (12,23), (13,14), (13,22), (14,21), (15,21), (15,18), (16,17), (16,18), (17,19), (18,20), (19,20), (20,21), (20,35), (20,36), (21,33), (22,26), (22,27), (23,25), (23,26), (24,25), (25,30), (26,31), (27,32), (27,33), (28,29), (28,30), (29,30), (29,43), (30,31), (32,42), (33,34), (35,38), (36,37), (36,38), (37,39), (38,39), (38,41), (38,40), (39,40), (39,47), (40,46), (34,41), (42,43), (42,44), (43,44), (44,55), (45,55), (45,52), (46,51), (47,48), (47,50), (48,49), (48,50), (49,50), (50,51), (51,52), (52,53), (53,54), (53,55), (55,56), (34,42), (34,45), (35,36)]
villes = ["Bayonne","Calais", "Lille", "Strasbourg", "Metz", "Reims", "Amiens", "Rouen", "Caen", "Cherbourg", "Brest", "Rennes", "Le Mans", "Chartres", "Paris", "Troyes", "Nancy", "Mulhouse", "Langres", "Besancon", "Dijon", "Sens", "Orleans", "Angers", "Vannes", "Nantes", "Tours", "Vierzon", "La Rochelle", "Saintes", "Niort", "Poitiers", "Limoges", "Vichy", "Clermont-Fd", "Macon", "Bourg-en-Bresse", "Geneve", "Lyon", "Grenoble", "Valence", "St-Etienne", "Brive", "Bordeaux", "Montauban", "Millau", "Avignon", "Digne-les-bains", "Nice", "Toulon", "Marseille", "Nimes", "Montpellier", "Narbonne", "Perpignan", "Toulouse", "Pau"]
xcoord = [290536, 566071, 651612, 999047, 880353 ,723240, 597143, 510403, 402458, 314252, 95121, 300873, 440320, 537130, 600217, 729191, 883209, 974902 ,824164, 878735, 804396, 670312, 567614, 382062, 217305, 305434, 475537, 579635, 330654, 368990, 384445, 446713, 516171, 684134, 658346, 792006, 822968, 883940, 795064, 866204, 801797, 760350, 536794, 369261, 521176, 659532, 798657, 912156, 997330, 892862, 846478, 762898, 724347, 654405, 645736, 527848, 379897]
ycoord = [1839901, 2661635, 2626710, 2412056, 2464886, 2475527, 2543973, 2494702, 2468337, 2522966, 2398724, 2353777, 2336045, 2383537, 2428986, 2368090, 2417370, 2317371, 2322392, 2255629, 2261516, 2355793, 2322615, 2278686, 2307396, 2253671, 2267165, 2246988, 2134800, 2087224, 2151008, 2177518, 2093169, 2125746, 2086868, 2148329, 2137972, 2144712, 2087442, 2026343, 1995763, 2050138, 2017905, 1986275, 1891190, 1899931, 1886158, 1906979, 1868053, 1798209, 1815584, 1872733, 1846609, 1798260, 1743848, 1845136, 1814694]
Ces données modélisent un réseau simplifié du réseau autoroutier français. On dispose d'une liste d'arcs (ici des autoroutes) qui permet de connaitre les sommets (ici des villes) qui sont directement reliés par une autoroute. Ainsi, la première liste de la liste arcs permet de savoir que le sommet 0 est relié au sommet 43 par une autoroute. La liste villes permet d'utiliser les valeurs des sommets comme index pour connaitre les villes concernées par ces relations autoroutières. Ainsi, villes[0] correspond à Bayonne et villes[43] correspond à Bordeaux, il existe donc une relation autoroutière entre Bayonne et Bordeaux. A partir du nom d'une ville, on peut obtenir son index avec la fonction index(). Ce découpage entre d’une part les relations (ici la liste des arcs) et les sommets (ici la liste des villes) est très classique pour modéliser un réseau. Pour compléter ces données géographiques, on trouvera aussi une liste de coordonnées x appelée xcoord et une liste de coordonnées y appelée ycoord. L'index de ces listes correspond à nos villes, ainsi xcoord[0] et ycoord[0] correspondent aux coordonnées x,y de Bayonne (290536, 1839901). Cette structure de données rend très facile la représentation graphique des villes avec matplotlib...
import matplotlib.pyplot as plt
plt.plot(xcoord,ycoord,'o')
plt.axis('equal')
plt.show()
En revanche, pour représenter les arcs c'est un petit peu plus compliqué... En fait, pour représenter une ligne en Python avec Matplotlib, il faut lui préciser les coordonnées X des deux points de cette ligne dans une première liste et les coordonnées Y des deux points de cette ligne dans une deuxième liste. Pour afficher le premier arc, on peut donc faire comme ci-dessous :
# Représentation premier arc
ptdep = arcs[0][0] # Récuperartion premier sommet du premier arc
ptarr = arcs[0][1] # Récuperartion deuxième sommet du premier arc
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[0],ycoord[1]]) # Affichage du premier arc
plt.show()
# Représentation premier arc dans le reseau
ptdep = arcs[0][0] # Récuperartion premier sommet du premier arc
ptarr = arcs[0][1] # Récuperartion deuxième sommet du premier arc
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]]) # Affichage du premier arc
plt.plot(xcoord,ycoord,'o') # Affichage des villes
plt.axis('equal')
plt.show()
Ensuite, le plot matplotlib est par défaut conçu pour représenter des segments continus (une ligne continue). Ici, il faut donc partir de Bordeaux pour tracer un nouvel arc. Par exemple, dans la liste arcs, il y a un arc qui relie Bordeaux (43) et Montauban (44). On peut donc récupérer les coordonnées de Montauban et les insérer dans les deux listes à la suite de celles des deux premières villes.
plt.plot([xcoord[0],xcoord[43],xcoord[44]],[ycoord[0],ycoord[43],ycoord[44]]) # Affichage de deux arcs en une ligne continue.
plt.plot(xcoord,ycoord,'o')
plt.axis('equal')
plt.show()
On le voit, on pourrait avoir deux listes (de coordonnées X et de coordonnées Y) qui permettent de dessiner le réseau avec une ligne continue, mais ce ne serait pas une solution élégante et ce serait surtout une solution relativement complexe pour une problématique simple. Cela générerait de plus des redondances, sauf si le réseau est un graphe eulérien, ce qui n'est pas le cas ici. Il semble plus simple de reprendre le code du tracé de l'arc initial et de faire défiler les arcs un à un pour les dessiner les uns après les autres.
# Arc 0
ptdep = arcs[0][0] # Récuperation premier sommet du premier arc
ptarr = arcs[0][1] # Récuperation deuxième sommet du premier arc
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]]) # Affichage du premier arc
# Arc 1
ptdep = arcs[1][0] # Récuperation premier sommet du deuxième arc
ptarr = arcs[1][1] # Récuperation deuxième sommet du deuxième arc
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]]) # Affichage du deuxième arc
plt.plot(xcoord,ycoord,'o') # Affichage des villes
plt.axis('equal')
plt.show()
Le problème, c'est qu'il y a 87 arcs. Le code serait donc très long à écrire. On va donc utiliser une boucle pour faire varier les index des arcs. Il me semble que c’est beaucoup plus simple et propre comme cela.
len(arcs)
87
for i in range(len(arcs)):
ptdep = arcs[i][0] # Recuperartion premier sommet
ptarr = arcs[i][1] # Recuperartion deuxième sommet
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]]) # Affichage arc
plt.plot(xcoord,ycoord,'o') # Affichage point
plt.axis('equal')
plt.show()
On a plus qu'à gérer les couleurs.
for i in range(len(arcs)):
ptdep = arcs[i][0] # Recuperartion premier sommet
ptarr = arcs[i][1] # Recuperartion deuxième sommet
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]], color='grey') # Affichage arc
plt.plot(xcoord,ycoord,'o') # Affichage point
plt.axis('equal')
plt.show()
Le parcours des arcs peut permettre au passage l’ajout d’informations supplémentaires, par exemple le calcul des longueurs des arcs. On peut stocker ces distances dans une liste arcs_dist. On peut aussi mettre en place des tests et des conditions pour distinguer les petits arcs et les grands arcs.
arcs_dist = []
for i in range(len(arcs)):
ptdep = arcs[i][0] # Recuperartion premier sommet
ptarr = arcs[i][1] # Recuperartion deuxième sommet
dist = np.sqrt((xcoord[ptdep]-xcoord[ptarr])**2 + (ycoord[ptdep]-ycoord[ptarr])**2)
arcs_dist = arcs_dist + [dist]
if dist >= 150000 :
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]], color='red') # Affichage grands arcs (>= 150km)
if dist < 150000 :
plt.plot([xcoord[ptdep],xcoord[ptarr]],[ycoord[ptdep],ycoord[ptarr]], color='grey') # Affichage petits arcs (< 150km)
plt.plot(xcoord,ycoord,'o') # Affichage point
plt.axis('equal')
plt.show()
Les fonctions¶
En Python, une fonction est un bloc de code réutilisable qui effectue une tâche spécifique. Pour définir une fonction, on utilise le mot-clé def, suivi du nom de la fonction et de parenthèses (). Les parenthèses peuvent contenir des paramètres, qui sont des valeurs que la fonction reçoit en entrée. Après les parenthèses, on met deux-points : pour indiquer le début du bloc de code de la fonction. Le code de la fonction est indenté, c'est-à-dire qu'il est décalé vers la droite. Si la fonction doit renvoyer une valeur, on utilise le mot-clé return, suivi de la valeur à renvoyer. On peut aussi simplement y insérer des print(). Une fois définie, la fonction peut être appelée en utilisant son nom suivi de parenthèses, en fournissant les arguments nécessaires si la fonction a des paramètres.
Désormais, il convient de travailler depuis l'éditeur syntaxique de spyder, si vous n'utilisez pas le notebook.
#Definition de la fonction get_dataset
def get_dataset() :
a = 2
print(a)
#Appel de la fonction
get_dataset()
2
La fonction get_dataset() ci-dessus va nous permettre de créer un jeu de données (x et y) qui va suivre une relation linéaire (y = a x + b) avec un peu de bruit. Pour cela, dans un premier temps, on peut rajouter un paramètre n à notre fonction pour définir la taille du jeu de données. On utilisera return pour pouvoir récupérer les données créées. Les valeurs de X sont choisis aléatoirement à l'aide ici de random.rand() de la bibliothèque numpy.
import numpy as np
def get_dataset(n) :
x = np.random.rand(n, 1)
return x
data_x = get_dataset(10)
print(data_x)
[[0.74702889] [0.97554158] [0.25498266] [0.89877427] [0.8013071 ] [0.05854318] [0.65964324] [0.21440886] [0.92141329] [0.86210517]]
Ensuite, on peut rajouter deux parametres (a et b) pour permettre à l'utilisateur de définir la relation linéaire souhaitée. On retourne alors x et y. On récupère ces deux objets (variables) que l'on peut afficher dans un plot.
import numpy as np
import matplotlib.pyplot as plt
def get_dataset(n, a , b) :
x = np.random.rand(n, 1)
y = a * x + b
return x, y
#Jeu de données de 100 points avec pour equation y = 2X + 10
data_x, data_y = get_dataset(100, 2, 10)
plt.scatter(data_x,data_y) #Ici on utilise scatter() et non plot() pour faire un le nuage de points.
<matplotlib.collections.PathCollection at 0x174c8846cd0>
On peut alors rajouter un paramètre bruit pour complexifier le dataset. Pour éviter que notre dataset reste compris entre x = 0 et x = 1, on peut aussi demander un argument xmax.
import numpy as np
import matplotlib.pyplot as plt
def get_dataset(n, a , b, bruit, xmax) :
x = xmax * np.random.rand(n, 1)
y = (a * x + b) + bruit * np.random.rand(n, 1)
return x, y
#faible bruit (bruit = 1)
data_x, data_y = get_dataset(100, 2, 5, 1, 10)
plt.scatter(data_x,data_y)
plt.show()
#bruit plus important (bruit = 5)
data_x, data_y = get_dataset(100, 2, 5, 3, 10)
plt.scatter(data_x,data_y)
plt.show()
Il est possible de rendre facultatif certains paramètres en rajoutant des valeurs par défaut dans notre fonction.
import numpy as np
import matplotlib.pyplot as plt
def get_dataset(a , b, n = 100, bruit = 5, xmax = 10) :
x = xmax * np.random.rand(n, 1)
y = (a * x + b) + bruit * np.random.rand(n, 1)
return x, y
data_x, data_y = get_dataset(2, 5)
plt.scatter(data_x,data_y)
plt.show()
Pour faire plus geek, pour appeler vos fonctions, il est possible de les insérer dans un test vérifiant que le code est éxécuté directement, ce qui est le cas ici. Ce bloc de code permet de définir un point d'entrée pour votre script. Vous pouvez y placer le code principal de votre programme, les appels de fonctions, les tests, etc. Il permet de rendre votre script réutilisable en tant que module. Si vous importez votre script dans un autre script, le code principal ne sera pas exécuté (ce qui est dans le test), ce qui évite les effets indésirables.
import numpy as np
import matplotlib.pyplot as plt
def get_dataset(a , b, n = 100, bruit = 5, xmax = 10) :
x = xmax * np.random.rand(n, 1)
y = (a * x + b) + bruit * np.random.rand(n, 1)
return x, y
if __name__ == "__main__":
data_x, data_y = get_dataset(2, 5, n = 500)
plt.scatter(data_x,data_y)
plt.show()
Les fonctions peuvent être insérées dans des classes, on les appelera alors des méthodes et les données de ces classes sont appelées des arguments. En Python, une classe est un est un plan pour créer des objets. C'est un concept fondamental de la programmation orientée objet que l'on ne va pas détailler ici. On va simplement placer notre fonction dans une classe puis l'appeler en dehors de celle-ci (de manière statique).
class ai() :
def get_dataset(a , b, n = 100, bruit = 5, xmax = 10) :
x = xmax * np.random.rand(n, 1)
y = (a * x + b) + bruit * np.random.rand(n, 1)
return x, y
dx, dy = ai.get_dataset(2, 5)
plt.scatter(dx,dy)
plt.show()
Généralement on trouvera dans ces classes une méthode init().
class ai() :
def __init__(self):
pass
def get_dataset(a , b, n = 100, bruit = 5, xmax = 10) :
x = xmax * np.random.rand(n, 1)
y = (a * x + b) + bruit * np.random.rand(n, 1)
return x, y
Ici, on peut y rajouter une fonction qui calcule par descente de gradients les coefficients de la régressions linéaire correspondant au jeu de données.
class ai() :
def __init__(self):
pass
def get_dataset(a , b, n = 100, bruit = 5, xmax = 10) :
x = xmax * np.random.rand(n, 1)
y = (a * x + b) + bruit * np.random.rand(n, 1)
return x, y
def gradient_descent(X, Y, a_init = 0, b_init = 0, lr = 0.01, epochs = 1000):
a, b = a_init, b_init
m = len(Y)
cost_history = []
for epoch in range(epochs):
predictions = a * X + b
error = predictions - Y
a_gradient = - (1 / m) * np.sum(error * X)
b_gradient = - (1 / m) * np.sum(error)
a = a + (lr * a_gradient)
b = b + (lr * b_gradient)
pre = a * X + b
cost_history.append((1 / (2 * m)) * np.sum((pre - Y) ** 2))
return a, b, cost_history
data_x, data_y = ai.get_dataset(2, 10)
a, b, cost_history = ai.gradient_descent(data_x, data_y)
plt.scatter(data_x, data_y)
r = np.array([np.min(data_x), np.max(data_x)])
plt.plot(r, a*r + b, c='red', linestyle='dashed')
plt.show()