Python pour l’analyse de données S’il est un domaine dans lequel Python excelle c’est bien celui de l’analyse de données (→ data science). Dans cette activité, vous allez vous familiariser avec l’utilisation des librairies essentielles pour l’analyse des données : matplotlib numpy pandas Généralités Comme précisé sur Wikipedia : L’analyse des données permet de traiter un nombre très important de données et de dégager les aspects les plus intéressants de la structure de celles-ci. Le succès de cette discipline dans les dernières années est dû, dans une large mesure, aux représentations graphiques fournies. Ces graphiques peuvent mettre en évidence des relations difficilement saisies par l’analyse directe des données ; mais surtout, ces représentations ne sont pas liées à une opinion “à priori” sur les lois des phénomènes analysés contrairement aux méthodes de la statistique classique. — Wikipedia L’analyse de données revêt donc une grande importance lorsqu’il s’agit de : prendre des décisions à partir de certains indicateurs anticiper des phénomènes — certains diront “prédire l’avenir”… — en détectant des tendances dans un ensembles de données ou dataset provenant d’une ou plusieurs sources (phénomène de corrélation). De nos jours, l’analyse de données est populaire puisqu’elle constitue la brique de base pour le machine learning (→ apprentissage automatique) puis le deep learning (→ apprentissage profond) dans des secteurs comme l’intelligence artificielle, la robotique, la reconnaissance automatique (vocale, faciale, de forme…). Figure 1. Décomposition d’un projet de machine learning : Dans l’illustration ci-dessus, on s’aperçoit que plusieurs tâches sont requises pour aboutir à l’analyse de données (Find Insights and Visualize) : Définir l’objectif Récolter les données Nettoyer les données Enrichir les données Sélectionner les informations pertinentes & les visualiser (voir 7 Fundamental Steps to Complete a Data Analytics Project pour plus de détails sur ces étapes) Dans l’analyse de données, plusieurs “profils métier” existent pour prendre en charge les tâches associées aux différentes phases d’un projet : Au niveau de la visualisation des données, une multitude de graphes existe et Python est capable de tracer la grande majorité d’entre eux : Le choix d’un graphe en particulier sera dicté par ce que l’analyste veut montrer, comprendre ou expliquer sur les données qu’il étudie. Matplotlib Présentation générale Matplotlib est la bibliothèque Python de tracé de graphique la plus populaire. Cette librairie couvre de nombreux types de graphes : lignes, courbes, histogrammes, points, barres, camembert, tableaux, polaires, … Un aperçu des courbes qu’elle peut tracer est consultable sur Matplotlib > Gallery De manière générale, à chaque type de graphique est associé une fonction (ex : hist() pour un histogramme) à laquelle il va falloir fournir — pour une courbe à 2 dimensions — les points correspondant aux abscisses puis ceux correspondant aux ordonnées. Ces points peuvent être fournis sous forme de tuples ou listes Python mais, dans la réalité, on les passera souvent sous forme de ndarrays ou de series ou bien encore de dataframes qui sont des types d’ensemble de données définis dans les librairies Numpy et Pandas qui seront présentées plus loin. Même si sa mise en œuvre est simple pour un usage basique, la structure de Matplotlib et son fonctionnement interne font que son utilisation peut être déroutante dans le cadre de tracés plus complexes si on ne maîtrise pas ces concepts de base. Utilisation basique Admettons que l’on veuille tracer la fonction suivante sous forme de courbe puis de points : Le code Python sera alors : import matplotlib.pyplot as plt (1) x = range(0, 10) (2) # ou # x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (2) # ou # x = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9) (2) y = [elt**2 for elt in x] # y <- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] (3) plt.figure() (4) plt.plot(x,y) (5) plt.show() (6) plt.figure() (7) plt.scatter(x,y) (8) plt.show() (9) 1 On importe le module de tracé de Matplotlib 2 On définit les points des abscisses sous forme d’une liste ou de tuple Python 3 On définit les ordonnées (ici avec une compréhension de liste Python) 4 On crée une figure dans laquelle va prendre place le tracé sous forme de courbe 5 On trace la courbe 6 On affiche la figure 7 On crée une nouvelle figure dans laquelle va prendre place le tracé sous forme de points 8 On trace les points 9 On affiche la figure On obtient alors : Si on veut afficher plusieurs graphiques sur une même figure, on utilise la fonction subplot(<nb-lignes>, <nb-colonnes>, <position>) entre chaque tracé pour spécifier le graphique de la figure sur lequel doit prendre place le tracé. Figure 2. Exemples d’appel à subplot() pour spécifier un graphique dans différents types de grilles (2 lignes, 2 colonnes, 2 lignes+2 colonnes) Pour tracer les 2 courbes de notre exemple dans 2 graphiques côte-à-côte, le code Python devient alors : import matplotlib.pyplot as plt x = range(0, 10) y = [elt**2 for elt in x] # y <- [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] plt.figure() plt.subplot(1,2,1) (1) plt.plot(x,y) plt.subplot(1,2,2) (2) plt.scatter(x,y) plt.show() 1 On spécifie que l’on veut tracer la courbe sur le 1er graphique (→ 3ème argument) d’une grille qui en comporte 2 (→ 2ème argument) sur une seule ligne (→ 1er argument) 2 On spécifie que l’on veut tracer la courbe sur le 2ème graphique (→ 3ème argument) d’une grille qui en comporte 2 (→ 2ème argument) sur une seule ligne (→ 1er argument) 🖮 Travail n° 1 Tracé simple Donner le code Python qui permet de tracer l’une sur l’autre dans la même figure les 2 fonctions et la fonction exponentielle fait partie du module math de Python et se nomme exp(). Résultat attendu : Annotation des graphiques Matplotlib dispose d’un ensemble de fonctions qui vont permettre de faciliter la compréhension d’un graphique en lui ajoutant : un titre → plt.title() des libellés sur les axes → plt.xabel() et plt.ylabel() un quadrillage → plt.grid() une légende → plt.legend() du texte à une position quelconque → plt.text() … On peut aussi personnaliser l’aspect du tracé avec les paramètres optionnels de la fonction plt.plot() : la couleur des points ou de la ligne → paramètre color l’épaisseur de la ligne → paramètre linewidth le type de ligne (continue, pointillées…) → paramètre linestyle le type de marqueur utilisé pour les points (cercle plein, carré…) et sa couleurs → paramètres marker et markercolor 🖮 Travail n° 2 Annotation de graphique À partir de la documentation officielle de la méthode plot() ou de tout autre ressource de votre choix, donner le code Python qui permet d’aboutir au graphique suivant : Numpy Présentation générale Numpy est une bibliothèque Python qui met à disposition une multitude de fonctions performantes qui permettent essentiellement de manipuler des tableaux de nombres. Au cœur de cette librairie on trouve un nouveau type de donnée nommé ndarray. Celui-ci correspond globalement à une liste Python — à une ou plusieurs dimensions — mais en beaucoup plus performant en terme de vitesse et d’occupation mémoire. Cependant, contrairement à une liste Python un ndarray ne peut contenir que des valeurs de même type (c.-à-d. tableau d’entiers, de nombres flottants…). Figure 3. Exemples de données numériques représentables avec un ndarray Figure 4. Exemple de comparaison de vitesse d’exécution du calcul du carré des 750000 premiers entiers (de 0 à 649999) sur un ndarray et une liste Python Outre la performance, on aura également accès à beaucoup plus de fonctions utiles dans le calcul scientifique que celles proposées pour les listes Python. Lorsqu’on utilise un ndarray, il est essentiel de savoir quelle est sa “géométrie” pour savoir comment accéder à ses éléments. Cette information nous est retournée par l’attribut shape d’un ndarray. Celui-ci est un tuple dont les constituants indique le nombre de cases du ndarray selon les différents axes de celui-ci. Dans un ndarray à 2 dimensions, l’axe 0 est celui des lignes et l’axe 1, celui des colonnes comme l’illustre la figure suivante (qui fait également apparaître un ndarray à 1 puis à 3 dimensions). Lorsqu’on voudra accéder aux éléments d’un ndarray (1 ligne, 1 colonne, 1 case, 1 ensemble de cases attenantes dans plusieurs lignes et colonnes), ce n° d’axe indiquera le rang de l’indice à spécifier pour extraire les données désirées. Exemple d’accès aux éléments d’un ndarray à 2 dimensions : ndarrayNumpy = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]]) print(f'. le tableau : \n{ndarrayNumpy}') print(f'. ses dimensions : {ndarrayNumpy.shape}') thirdLine = ndarrayNumpy[2] print(f'. sa 3ème ligne : {thirdLine}') print(f'. le 2ème élément de sa 4ème ligne : {ndarrayNumpy[3, 1]}') print(f'. les 2 derniers élements des 2 dernières lignes :\n {ndarrayNumpy[-2:, -2:]}') """ Affiche : . le tableau : [[ 1 2 3] [ 4 5 6] [ 7 8 9] [10 11 12]] . ses dimensions : (4, 3) . sa 3ème ligne : [7 8 9] . le 2ème élément de sa 4ème ligne : 11 . les 2 derniers élements des 2 dernières lignes : [[ 8 9] [11 12]] """ Domaines d’utilisation de Numpy : le calcul matriciel (→ ndarray de dimension 2) Ainsi, pour faire le calcul suivant : Il suffira de taper le code suivant import numpy as np A = np.array([[1, 2, 3], [4, 5, 6]]) B = np.array([[10, 11],[20, 21],[30, 31]]) M = np.dot(A, B) print(M) # Affiche : # [[140 146] # [320 335]] le traitement des images Noir&Blanc (→ ndarray de dimension 2) ou couleur (→ ndarray de dimension 3) Ci-dessous un exemple qui montre comment zoomer sur une image : from scipy import misc import matplotlib.pyplot as plt face = misc.face(gray=True) (1) plt.figure() plt.subplot(2,1, 1) plt.imshow(face,cmap=plt.cm.gray) (2) plt.subplot(2, 1, 2) h = face.shape[0] # hauteur de l'image w = face.shape[1] # largeur de l'image zoom_face = face[h//4 : -h//4, w//4 : -w//4] (3) plt.imshow(zoom_face,cmap=plt.cm.gray) (4) plt.show() 1 On stocke dans le ndarray nommé face l’image de référence — fournie dans le module misc de la librairie scipy — qui fait 1024x768 points 2 On affiche l’image complète 3 On crée un nouveau ndarray zoom_face qui contient uniquement les 512x384 points de la partie centrale de l’image originale (→ zoom x2) en utilisant la technique du slicing 4 On affiche l’image correspondant à zoom_face Initialisation d’un ndarray En dehors de l’initialisation avec array() vue plus haut dans l’exemple, Numpy propose d’autres fonctions pour créer des ndarrays soit depuis des données existantes soit en les générant lui même. Exemples : np.empty( (3,2) ) → crée un tableau de 3 lignes et 2 colonnes mais n’initialise pas son contenu np.zeros( (2,5) ) → crée un tableau de 2 lignes et 5 colonnes dont les cellules sont initialisées à 0 np.full((1, 3), 3.14) → crée un tableau de 1 ligne et 3 colonnes dont chaque cellule est initialisée avec la valeur 3.14 np.full((6, ), 1515 → crée un tableau de 6 cases dont chaque cellule prend la valeur 1515. np.genfromtxt('fichier.csv',delimiter=',',skip_header=1) → initialise un tableau avec le contenu d’un fichier csv (→ coma separated values) en omettant sa 1ère ligne Accès aux éléments d’un ndarray L’accès aux éléments d’un ndarray peut se faire de multiples façons : l'indexing le slicing le boolean indexing L'indexing C’est la façon la plus intuitive pour accéder à la valeur contenue dans une case d’un nd_array. Elle utilise simplement les indices de cette case sur les différents axes. Exemple : Soit le ndarray à 3 dimensions suivant : On désire accéder aux valeurs 44, 74 et 25. Le code Python correspondant est : >>> import numpy as np >>> values = np.array([[[12, 25, 33] ... , [74, 11, 64] ... , [88, 29, 77]] ... ... , [[44, 58, 42] ... , [-1, -1, 18] ... , [-1, -1, 97]] ... ... , [[68, 81, 22] ... , [-1, -1, 37] ... , [-1, -1, 21]]]) >>> values[1][0][0] # 2ème case axe 0 44 >>> values[0][1][0] # 2ème case axe 1 74 >>> values[0][0][1] # 2ème case axe 2 25 Voir la vidéo suivante pour une explication de l’indexing sur un ndarray à 2 dimensions. ✎ Accéder aux cases d’un ndarray en utilisant l'indexing Quelles instructions faut-il saisir pour accéder aux valeurs : 81 ? 18 ? 21 ? 64 ? Solution >>> values[2][0][1] 81 >>> values[1][1][2] 18 >>> values[2][2][2] 21 >>> values[0][1][2] 64 >>> Le slicing Le slicing — comme son nom l’indique (slice signifie “tranche”) — va permettre de découper un ndarray de façon à en récupérer qu’une partie. Ceci sera obtenu en spécifiant entre crochets un index de début et un index de fin (séparés par ‘:’) sur chacun des axes du ndarray que l’on souhaite découper. Exemple : Dans le ndarray représenté ci-dessous, les valeurs des cases bleues seront extraites avec l’instruction values[0:1, 1:3, 0:4] >>> values[0:1, 1:3, 0:4] array([[[74, 11, 64], [88, 29, 77]]]) Noter que la case spécifiée par l’indice de fin n’est pas incluse dans le résultat du slicing (on s’arrête à la case d’avant) Lorsqu’on omet l’indice de départ, la zone découpée commence au 1er élement de l’axe du ndarray. De même, si on omet l’indice de fin, la zone découpée finit au dernier élément de l’axe du ndarray En conséquence, si on omet les 2, et donc qu’on spécifie seulement ‘:’, c’est l’ensemble des cellule de l’axe considéré qui est retourné. Exemple : Extraction des portions colorées dans le ndarray suivant >>> values[:2,0:1,0:1] # axe 0, 1ère case -> 2ème case array([[[12]], [[44]]]) >>> values[1:2,1:,2:3] # axe 1, 2ème case -> dernière case array([[[18], [97]]]) >>> values[0:1,:,1:2] # axe 1, toutes les cases array([[[25], [11], [29]]]) On ne le traitera pas ici mais d’autres syntaxes sont encore possibles (indice négatif, spécification de saut). Voir l’article Python Slice Notation: Quick Explanation ou alors la vidéo suivante pour une illustration et un résumé des syntaxes possibles : ✎ Accéder aux cases d’un ndarray en utilisant le slicing Donner les instructions qui permettent d’extraire les portions ci-dessous du ndarray utilisé pour l’exemple précédent : array([[[64],[77]], [[18],[97]], [[37],[21]]]) ? array(\[\[[25, 33]], \[[58, 42]]]) ? Solution >>> values[0:4, 1:3, 2:3] array([[[64], [77]], [[18], [97]], [[37], [21]]]) >>> values[0:2, 0:1, 1:3] array([[[25, 33]], [[58, 42]]]) Le boolean indexing La dernière façon d’accéder à des des portions de ndarray consiste à cibler uniquement les cases qui répondent à certaines conditions. Ceci se fera en spécifiant une expression booléenne à l’intérieur de la paire de crochets plutôt que des indices. Lorsqu’on extrait des cases d’un ndarray avec le boolean indexing, la géométrie (→ shape) du tableau retourné n’est pas conservée. Exemple : sélectionner les cases dont la valeur est supérieure à 50 dans le ndarray suivant : >>> values[values > 50] array([74, 64, 88, 77, 58, 97, 68, 81]) # !! tableau à une seule dimension !! Par contre, l’accès en écriture dans un ndarray à l’aide du boolean indexing ne modifie pas la géométrie du ndarray d’origine Exemple : Remplacer par 0 toutes les valeurs de ce même ndarray supérieures à 50 >>> values[values>50] = 0 >>> values array([[[12, 25, 33], [ 0, 11, 0], [ 0, 29, 0]], [[44, 0, 42], [-1, -1, 18], [-1, -1, 0]], [[ 0, 0, 22], [-1, -1, 37], [-1, -1, 21]]]) 🖮 Travail n° 3 Télécharger localement le fichier tmin-tmax-marseille-052021.csv qui contient l’ensemble des températures minimales et maximales relevées chaque jour du mois d’avril 2021 à Marseille. Inspecter le contenu de ce fichier et répondre aux questions Ce fichier est au format csv (pour coma separated values c.-à-d. “valeurs séparées par des virgules”). À quoi correspond chaque ligne ? Quel caractère est utilisé en tant que séparateur décimal pour les températures ? Ouvrir un nouveau notebook Jupyter et charger dans un ndarray l’ensemble des températures minimales et maximales relevées chaque jour du mois d’avril 2021 à Marseille depuis le fichier tmin-tmax-marseille-052021.csv . Pour cela, vous pourrez utiliser la fonction loadtxt() de Numpy : Exemple: tempMarseille = np.loadtxt('tmin-tmax-marseille-052021.csv', delimiter=',', skiprows=1) (1) 1 charge les données du fichier dans le ndarray tempMarseille en utilisant le ‘,’ comme séparateur des valeurs et en omettant la 1èreligne qui contient le titre des colonnes Consulter la documentation des fonctions mathématiques de Numpy (→ Mathematical functions ) ainsi que celles des fonctions statistiques (→ Statistics ) et sélectionner la bonne fonction pour déterminer : la température la plus basse du mois la température la plus élevée du mois la température la plus froide qu’il a été donné de mesurer au plus chaud de la journée (c.-à-d. la température maximale la plus froide) les moyennes (mean en anglais) des températures minimales et maximales sur le mois Tracer sur une même courbe pour l’ensemble du mois les températures minimales, maximales et moyennes pour chaque journée. Solution >>> import os >>> tempMarseille = np.loadtxt(os.environ['USERPROFILE']+'\\Downloads\\tmin-tmax-marseille-052021.csv', delimiter=',', skiprows=1) >>> np.amin(tempMarseille[:,0:1]) (1) -3.9 >>> np.amin(tempMarseille, axis=0)[0] (1) -3.9 >>> np.amax(tempMarseille[:,1]) (2) 16.9 >>> np.amax(tempMarseille) (2) 16.9 >>> np.amin(tempMarseille[:,1]) (3) 2.8 >>> np.mean(tempMarseille, axis=0) (4) array([ 1.92 , 10.29666667]) >>> np.mean(tempMarseille, axis=0)[0] (5) 1.9199999999999997 >>> np.mean(tempMarseille[:,1]) (6) 10.296666666666665 >>> import matplotlib.pyplot as plt >>> x = range(1,31) >>> tmin = tempMarseille[:,0] >>> tmax = tempMarseille[:,1] >>> tmean = np.mean(tempMarseille, axis=1) >>> plt.figure() Warning: QT_DEVICE_PIXEL_RATIO is deprecated. Instead use: QT_AUTO_SCREEN_SCALE_FACTOR to enable platform plugin controlled per-screen factors. QT_SCREEN_SCALE_FACTORS to set per-screen DPI. QT_SCALE_FACTOR to set the application global scale factor. <Figure size 640x480 with 0 Axes> >>> plt.title('Températures relevées à Marseille en Mai 2021') Text(0.5, 1.0, 'Températures relevées à Marseille en Mai 2021') >>> plt.plot(x,tmin,label='t° min') [<matplotlib.lines.Line2D object at 0x00000173B7635FC0>] >>> plt.plot(x,tmax,label='t° max') [<matplotlib.lines.Line2D object at 0x00000173B7636320>] >>> plt.plot(x,tmean,label='t° moyennes journalières') [<matplotlib.lines.Line2D object at 0x00000173B76362C0>] >>> plt.legend() <matplotlib.legend.Legend object at 0x00000173B7636830> >>> plt.show() 1 la température la plus froide (plusieurs façons) 2 la température la plus élevée (plusieurs façons) 3 la température la plus froide relevée au plus chaud de la journée 4 moyennes des températures min. et max. 5 moyenne des températures min. 6 moyenne des températures max. Le code source sous forme de notebook Jupyter. Pandas dépend du package mkl dont la taille est de 115Mo meteostat 🞄 🞄 🞄 Prise en main de Python SN2IR - Design patterns