Vous êtes sur la page 1sur 283

Data

science avec Python

Luca Massaron
John Paul Mueller



Data science avec Python pour les Nuls

Titre de l’édition originale : Python For Data Science For Dummies, 2nd Edition
Copyright © 2019 Wiley Publishing, Inc.

Pour les Nuls est une marque déposée de Wiley Publishing, Inc.
For Dummies est une marque déposée de Wiley Publishing, Inc.

Édition française publiée an accord avec Wiley Publishing, Inc.
© Éditions First, un département d’Édi8, Paris, 2019.

Éditions First, un département d’Édi8
12, avenue d’Italie
75013 Paris – France
Tél. : 01 44 16 09 00
Fax : 01 44 16 09 01
Courriel : firstinfo@editionsfirst.fr
Site Internet : www.pourlesnuls.fr

ISBN : 978-2-412-05072-9
ISBN numérique : 9782412053461
Dépôt légal : novembre 2019.

Traduction : Olivier Engler
Mise en page : Enredos e Legendas Unip. Lda

Cette œuvre est protégée par le droit d’auteur et strictement réservée à l’usage privé du client. Toute
reproduction ou diffusion au profit de tiers, à titre gratuit ou onéreux, de tout ou partie de cette œuvre est
strictement interdite et constitue une contrefaçon prévue par les articles L 335-2 et suivants du Code de
la propriété intellectuelle. L’éditeur se réserve le droit de poursuivre toute atteinte à ses droits de
propriété intellectuelle devant les juridictions civiles ou pénales.

Ce livre numérique a été converti initialement au format EPUB par Isako www.isako.com à partir de
l'édition papier du même ouvrage.
Introduction
De plus en plus de domaines d’activité trouvent intérêt à exploiter des données informatiques,
et nombreux sont ceux qui en adoptent les techniques sans tambour ni trompette. Vous-même
générez de nouvelles données lors de chacune de vos visites du réseau Internet. La croissance
du réseau Internet est phénoménale, comme le montrent les statistiques
(https://www.internetworldstats.com/emarketing.htm). La science des données (nous
parlerons de datalogie dans ce livre) a pour but de transformer cet incroyable volume de
données en quelque chose d’utile, quelque chose qui puisse vous servir au quotidien pour
réaliser une vaste palette de tâches ou pour bénéficier de services proposés par d’autres, bref
pour transformer ces données en informations.
En fait, vous avez certainement déjà profité de la datalogie sans vous en rendre compte.
Lorsque vous utilisez votre navigateur Web pour chercher un site, vous avez obtenu des
réponses ainsi que des suggestions d’autres critères de recherche. Ces critères sont produits
par la datalogie. Celui qui a rendu visite à son médecin et s’est fait confirmer que cette petite
boule au coude n’était pas une tumeur a profité du fait que le médecin s’est appuyé pour son
diagnostic sur des données scientifiques. Autrement dit, vous manipulez des données au
quotidien sans vous en rendre compte. Ce livre a pour objectif de vous montrer comment
exploiter la datalogie pour réaliser différentes tâches pratiques, mais aussi de faire un tour
d’horizon de ses différents domaines d’emploi. Une fois que vous savez comment résoudre un
problème lié à des données et utiliser cette science, vous obtenez un sérieux avantage ; vous
augmentez ainsi vos chances de promotion si l’avancement de votre carrière vous importe.

Contenu du livre
Le principal objectif de ce livre est de vous libérer de toute angoisse face à cette science des
données. Il va vous montrer qu’il s’agit non seulement d’une science intéressante, mais qu’elle
est tout à fait à votre portée au moyen du langage Python. Vous pourriez croire qu’il faut être
un expert en informatique pour réussir un projet de datalogie, mais c’est absolument faux. Le
langage Python est livré avec une foule de librairies de fonctions qui vont réaliser tout le
travail ardu à votre place. Vous n’allez même pas vous rendre compte de tous les efforts qui
vous seront épargnés, et vous n’aurez pas besoin de vous en soucier. Ce qui vous reste à définir
est l’ensemble des tâches que vous voulez réaliser ; Python se charge de vous les rendre
accessibles.
Le livre prend soin de montrer l’importance qu’il y a à bien choisir ses outils. Vous allez
commencer avec Anaconda, un regroupement de logiciels qui combine IPython et Jupyter
Notebook, deux outils qui simplifient énormément le travail avec Python. Vous allez utiliser
IPython dans un environnement totalement interactif. Le code source que vous allez saisir dans
l’autre outil, Jupyter Notebook (que nous abrégerons en Notebook) aura un aspect visuel bien
peaufiné, et vous permettra de combiner des éléments de présentation dans le même
document que celui contenant le code source. L’approche est un peu différente de celle d’un
environnement de développement intégré EDI. Pour pouvoir expérimenter les exemples du
livre plus facilement dans d’autres plates-formes que Windows, nous présenterons un
environnement de développement appelé Google Colab qui va permettre d’essayer quasiment
tous les exemples du livre depuis une tablette ou (si l’écran n’est pas trop petit) votre
telliphone (smartphone).
Le livre va présenter un bon nombre de techniques intéressantes pour créer des graphiques à
partir de vos analyses au moyen de MatPlotLib, et le livre donne tous les détails pour y
parvenir. Nous avons également expliqué en détail les différentes ressources disponibles, et
notamment les paquetages. Vous verrez comment utiliser Scikit-learn pour effectuer des
traitements de données vraiment étonnants. Nombreux sont par exemple ceux qui aimeraient
savoir comment fonctionne la reconnaissance d’écriture manuscrite. Si vous en êtes, ce livre va
vous mettre le pied à l’étrier dans ce domaine.
Si vous vous inquiétez de la mise en place de cet environnement de travail, ce livre ne vous
laisse pas sur le bord du chemin. Dans les premiers chapitres, vous trouverez des instructions
d’installation complètes pour Anaconda, puis un guide pour bien démarrer en datalogie avec
Jupyter Notebook comme avec Google Colab. Le but est de vous permettre de démarrer le plus
vite possible, en commençant avec des exemples simples pour que le code source ne soit
jamais un obstacle à votre progression.
Tous les exemples du livre utilisent la version 3.x du langage Python, qui est la plus récente.
Pour simplifier l’assimilation des différents concepts, nous avons établi quelques conventions :
» Le texte que vous devez saisir tel quel est présenté dans cette police en gras. La seule
exception concerne le graissage des entrées de liste dans les explications.
» Un mot imprimé en italique dans une séquence à saisir doit être remplacé par une valeur
réelle. Par exemple, lorsque vous lisez « Saisissez nom_util puis frappez Entrée », vous
devez bien sûr remplacer nom_util par votre nom d’utilisateur.
» Les adresses Web et les extraits de code dans le texte sont imprimés en police non
proportionnelle. Si vous lisez une version numérique du livre, vous pouvez visiter les sites
correspondant aux liens présentés dans le texte, comme ceci : http : www.site.com.
» Lorsque vous devez choisir une commande dans un menu, la séquence réalisée est
présentée comme ceci : Fichier/Nouveau.

Connaissances préalables supposées


Pour bien profiter du contenu de ce livre, quelques hypothèses ont été émises quant à vos
connaissances actuelles.
Tout d’abord, vous aurez intérêt à maîtriser la plate-forme que vous utilisez (Windows, Linux
ou macOS). Le Chapitre 3 explique comment installer Anaconda et le Chapitre 4 comment
utiliser Google Colab en ligne. Afin de rester concentré sur l’utilisation du langage Python dans
le domaine de la datalogie, nous ne faisons aucune remarque spécifique à une plate-forme
plutôt qu’une autre. Nous supposons que vous savez comment installer et utiliser une
application, et manipuler la plate-forme.
Vous devez avoir un minimum de connaissances du langage Python. Ce livre ne contient pas de
guide du débutant pour Python. Si nécessaire, procurez-vous le titre Python pour les Nuls dans
la même collection.
Ce livre ne contient pas de rappels de mathématiques ni de statistiques. Vous allez rencontrer
quelques exemples de formules mathématiques assez complexes, mais il suffira de les utiliser
tels quels pour rester collé à l’objectif consistant à découvrir Python en datalogie. Les
Chapitres 1 et 2 donnent plus de détails quant aux connaissances bienvenues pour profiter au
mieux du livre.
Nous supposons bien sûr que vous disposez d’un accès Internet. Vous trouverez de nombreux
liens dans le texte qui renvoient sur des pages de documentation en ligne permettant d’aller
plus loin dans le sujet concerné.
N. d. T. : La plupart des pages sont hélas en langue anglaise, mais nous fournirons le lien vers
une page équivalente en français, lorsque cela est possible.

Conventions typographiques du livre


Pour rendre plus aisée l’exploitation du contenu du livre, nous avons pris soin de mettre en
valeur certains termes par un codage visuel à portée sémantique, lorsqu’ils apparaissent dans
le corps du texte :
NomMotClé
Nous imprimons ainsi toutes les entités Java fondamentales définies par le langage : noms des
types, des méthodes, des fonctions, des opérateurs et des traits.
Commande
Codage utilisé pour les noms de commandes et d’options de menus.
NomFichier
Codage mettant en exergue les noms de fichiers et de répertoires (dossiers), ainsi que les
adresses Web et FTP.
Paramètre
Ce codage désigne les paramètres d’appel des méthodes qui sont remplacés par des noms
appropriés au programme réel selon les choix du développeur.
Concept
Mise en valeur de la première apparition d’un terme essentiel.
Cette icône marque un paragraphe contenant une astuce ou une précision.

Cette icône marque une mise en garde ou bien souligne un concept pouvant être à l’origine
d’une équivoque.

Cette icône marque une précision technique.

Cette icône indique des informations essentielles à retenir.

Fichiers source des exemples


Pour chaque chapitre, tous les codes source des exemples sont réunis dans un fichier calepin
(notebook). Tous les fichiers sont réunis dans une archive au format ZIP que nous vous invitons
à télécharger depuis la page dédiée au livre sur le site de l’éditeur :
www.editionsfirst.fr
Recherchez le titre du livre grâce à la zone de recherche, puis dans la fiche du livre, cliquez
sur Contenu additionnel, et enfin sur Télécharger.
N’hésitez pas à consulter l’éventuel fichier LISEZMOI.txt que vous trouverez dans l’archive
des exemples si des remarques complémentaires ou des points de mise à jour rendent
l’existence de ce fichier justifiée.

Trajectoire de lecture
Vous êtes prêt à vous lancer dans votre découverte de la datalogie avec Python ? Si vous n’avez
encore jamais abordé ce domaine, commencez dans l’ordre avec le Chapitre 1, puis progressez
au rythme qui vous permettra de bien assimiler tout le contenu.
Si vous êtes très impatient de découvrir ce qu’on peut faire avec Python, vous pouvez aller
immédiatement lire le Chapitre 3, tout en sachant que certains éléments mentionnés resteront
obscurs avant un chapitre ultérieur. Si vous avez déjà installé l’environnement Anaconda, vous
pouvez passer directement au Chapitre 5 (mais prenez le temps de survoler le Chapitre 3 pour
découvrir ce que nous présupposons en écrivant ce livre). Si vous comptez utiliser une tablette,
lisez le Chapitre 4 afin de bien connaître les contraintes de l’environnement Web de Google
Colab pour les exemples. Il ne permet en effet pas de les exécuter tous. Rappelons enfin que si
vous installez Anaconda, il est conseillé de disposer au moins de la version 3.6.5 de Python
pour obtenir de bons résultats avec des exemples.
Si vous avez déjà une bonne maîtrise de Python et avez installé Anaconda, vous pouvez ignorer
les premiers chapitres et vous rendre directement au Chapitre 5. Vous reviendrez en arrière si
nécessaire. Prenez le temps d’assimiler chaque technique avant de passer à la suivante. En
effet, chaque exemple de code source, chaque procédure et chaque technique représentent
une leçon indispensable et vous risqueriez de passer à côté d’un élément vital si vous
progressez trop vite.

Les auteurs
John Paul Mueller est auteur de plus de 100 livres techniques abordant les réseaux, la sécurité,
la gestion de bases de données et la programmation.
Luca Massaron est data scientiste spécialisé dans l’organisation et l’interprétation de données
big data pour les transformer en smart data. Il est expert GDE (Google Developer Expert) en
apprentissage machine.
Terminologie française
Tout au long du livre, vous trouverez quelques termes nouveaux. Parcourez le tableau suivant
pour prendre vos repères.
N. d. T. : Il est possible que d’éminents experts du domaine concerné soient inclinés à préférer
d’autres termes français ou à conserver l’anglais. Je serais ravi de lire leurs commentaires à
l’adresse fournie en fin d’introduction.

Terme utilisé Terme Commentaires si nécessaire


anglais
1 sur n one-hot Encodage binaire dont tous les bits sauf 1 sont forcés à 0. On
rencontre aussi « 1 parmi n ».
aberrant outlier Une valeur anormalement éloignée de la majorité des
observations.
acuité kurtosis
analyse en Principal Nous conservons le sigle anglais PCA.
composantes Component
principales Analysis
analyse Exploratory Nous conservons l’acronyme anglais EDA.
exploratoire de Data Analysis
données (EDA)
asymétrie skewness
binning binning Répartition de valeurs uniquement numériques en plusieurs bacs
(bins). Nous avons parfois utilisé le terme regroupement qui sert
aussi pour clustering mais la confusion n’est pas possible.
calepin (de notebook Le terme notebook sert d’abord à désigner un ordinateur portable
Jupyter) avec lequel on peut aussi prendre des notes. Le terme calepin
permet de bien distinguer le format de fichier hybride qui permet
de combiner des blocs de code source et des commentaires ou des
graphiques.
centroïde centroid Nous préférons centroïde à centre de masse.
creuse (matrice) sparse matrix
datalogie data science L’expression « science des données » tente de remplacer «
datascience ». « Datalogie » utilise le même mécanisme de
construction que « technologie ».
datalogue data scientist Personne produisant des informations essentielles (des discours,
logos, au sens linguistique) par distillation de flots de données
brutes.
débitage dicing Action de supprimer des données d’un tableau dans plusieurs
dimensions à la fois. Le « tranchage » (slicing) ne traite qu’une
dimension à la fois.
décomposition en Single Value Nous conservons le sigle anglais SVD.
valeurs Decomposition
singulières (SVD)
écart interquartile Interquartile Nous conservons le sigle anglais IQR.
(IQR) Range
ensachage bagging Notez que le terme anglais proviendrait d’une contraction de
Boostrap AGGregation-INGg.
erreur Mean Squared Nous conservons le sigle anglais MSE.
quadratique Error
moyenne
machine à Support Vector Nous conservons le sigle anglais SVM.
vecteurs de Machine (SVM)
support
mécapprentissage machine L’expression « apprentissage machine » va devenir tellement
learning (ML) usitée qu’un terme plus concis est le bienvenu.
moindres carrés Ordinary Least
ordinaires Squares
(méthode)
recherche grid search
systématique
regroupement clustering Technique de classification non supervisée.
sur-ajustement overfitting On utilise aussi « sur-apprentissage ».

Remerciements du traducteur (Danksagung)


Je remercie tout d’abord Hartmut et Heiderose pour leur patience pendant ces semaines de
production sur les terres de Salzgitter dans le land de Niedersachsen. Noch mal danke,
Hartmut und Haya !
Merci à Bernard Delaranche pour ses conseils, à mon éditeur Julien et à ma correctrice Anne
pour cette fructueuse collaboration dans un domaine prometteur.

Contacter le traducteur
Vous pouvez utiliser l’adresse suivante :
firstinfo@efirst.com
en commençant le sujet de votre message par la mention PYDASC.
PARTIE 1
Entrée en datalogie avec Python

DANS CETTE PARTIE :


» Comment le langage Python simplifie la datalogie (science des données)
» Les principales fonctions Python utilisées en datalogie
» Mise en place de votre atelier de création Python
» Google Colab pour utiliser d’autres terminaux
Chapitre 1
Pourquoi Python convient à la datalogie
DANS CE CHAPITRE :
» Les merveilles de la datalogie

» Comment fonctionne la datalogie

» Établir la connexion entre Python et la datalogie

» Bref tour d’horizon de Python

V ous pourriez croire que la science des données, que nous appelons datalogie, est une
technologie toute nouvelle pour vous ; c’est faux. Bien sûr, la datalogie suppose d’utiliser
des techniques statistiques sophistiquées et d’exploiter d’énormes volumes de données, le
Big Data. Mais la raison d’être de la datalogie est de vous aider à prendre les meilleures
décisions, à créer des suggestions pour de nouveaux choix basés sur les choix antérieurs,
même à permettre à un robot de reconnaître des objets. En réalité, la datalogie est utilisée
dans un tel nombre de domaines qu’il est impossible d’en trouver un qui ne va pas être impacté
par la datalogie. Autrement dit, la datalogie est cet instrument permettant de profiter des
merveilles de la technologie. Sans datalogie, la plupart des avantages dont vous profitez au
quotidien seraient inaccessibles. C’est une des raisons pour lesquelles un des métiers les plus
attirants en ce début de XXIe siècle est celui de datalogue, ou ingénieur en datascience.
Pour pouvoir pratiquer la datalogie sans être un génie des mathématiques, il faut se munir
d’outils. Il en existe un vaste choix, mais Python est particulièrement approprié pour simplifier
l’exploitation des données grâce à l’existence d’un nombre énorme de librairies de fonctions
mathématiques pour Python. C’est ce qui vous permet de réaliser certaines opérations sans
connaître en détail leur fonctionnement. D’autre part, le langage Python permet d’adopter
différents styles d’écriture (des paradigmes de programmation) ; vous pouvez bien sûr utiliser
un autre langage pour créer des applications de datalogie, mais Python vous simplifie le
travail. C’est donc un choix naturel pour tous ceux qui veulent privilégier l’intelligence au
labeur.
Ce chapitre vous propose un aperçu de ce qu’il est possible de faire avec Python. Il ne s’agit
pas d’un guide d’apprentissage du langage Python ; il va donner quelques conseils de base
dans la perspective de la datalogie. Si vous avez besoin d’une introduction au langage Python,
vous pouvez vous tourner vers le livre correspondant dans la même collection, Python pour les
Nuls.
CHOIX D’UN LANGAGE DE DATALOGIE

En un demi-siècle, ce sont des dizaines d’infolangages (langages de programmation) qui sont apparus,
la plupart étant dédiés à un domaine d’application spécifique, afin de simplifier l’automatisation d’un
ensemble d’activités. Il est donc indispensable de choisir le langage le mieux adapté à vos objectifs.
Un tournevis est préférable à un marteau pour serrer une vis. Les spécialistes de la science des
données ne choisissent que parmi un petit nombre d’infolangages. Voici ceux qui sont les plus utilisés
en datalogie, dans l’ordre décroissant des préférences :
» Python (polyvalent) : nombreux sont les datalogues qui adoptent Python à cause de son vaste
choix de librairies de fonctions, parmi lesquelles NumPy, MatPlotLib et Scikit-learn. Python est un
langage précis qui permet aisément de lancer des traitements en parallèle sur de grands
volumes de données, ce qui réduit les temps de traitement et d’analyse. La communauté des
développeurs en datalogie a produit plusieurs ateliers de développement appelés IDE, et
notamment Anaconda. Le concept de calepin interactif de l’outil Jupyter Notebook incorporé dans
Anaconda simplifie énormément les calculs scientifiques. (Nous verrons comment utiliser Jupyter
Notebook dans le Chapitre 3.) Le langage Python représente en outre un excellent intermédiaire
pour faire coopérer plusieurs langages tels que le C, le C++ ou le Fortran. La documentation de
Python explique comment créer les extensions nécessaires. Python simplifie les fonctions de
détection de motifs remarquables, qui permet par exemple à un robot de détecter un objet à
partir d’un bloc de pixels d’un flux vidéo. Python est également très utilisé dans bien d’autres
domaines scientifiques.
» R (orienté statistique) : ce langage partage de nombreuses caractéristiques avec Python, mais il
s’exprime d’une autre façon. D’ailleurs, certains programmeurs se servent de Python et de R de
façon interchangeable, parfois simultanément. R est fourni avec un environnement spécifique qui
évite d’avoir à installer un atelier tel qu’Anaconda. En revanche, R ne semble pas pouvoir
coopérer aussi bien que Python avec d’autres langages.
» SQL (gestion de bases de données) : le sigle anglais SQL signifie Structured Query Language ;
c’est un langage pour réaliser des requêtes dans des bases de données. Il est donc plus orienté
données que calculs. Les données sont devenues le capital par excellence des entreprises
modernes, et une excellente gestion des données est indispensable. Toute entreprise ayant un
minimum d’envergure se fonde sur des bases relationnelles, qui sont exploitées au moyen du
langage SQL. Les applications correspondantes sont des SGBDR (Système de Gestion de Base de
Données Relationnelle), en anglais DBMS. Le langage SQL offre un certain nombre de fonctions
d’analyse et de traitement des données. Certaines opérations de datalogie seront très
performantes de cette manière puisque l’accès aux données est direct. Les personnes qui se
chargent de la maintenance des bases (les administrateurs de bases de données), se servent
sans cesse du langage SQL pour maintenir leurs bases en bon état, mais vont rarement réaliser
des analyses approfondies. En revanche, les datalogues peuvent se servir du langage SQL pour
réaliser certaines tâches d’exploitation, et créer des scripts qu’ils peuvent ensuite rendre
disponibles aux administrateurs pour leurs propres besoins.
» Java (polyvalent) : certains projets de datalogie supposent de réaliser d’autres genres de
traitements informatiques, pour lesquels il est préférable d’utiliser un langage à usage général
répandu. Le langage Java offre une énorme quantité de librairies de fonctions, et la plupart ne
sont pas du tout orientées datalogie. De plus, c’est un langage qui se distingue par la qualité de
son orientation objet, si on le compare aux autres langages de cette liste. C’est un langage à
typage strict offrant des performances tout à fait correctes. Certains le privilégient donc pour les
versions définitives de leur projet. En revanche, Java n’est pas très pratique pour les phases
d’expérimentation et les interrogations à la volée.
» Scala (polyvalent) : le langage Scala se fonde sur la machine virtuelle JVM de Java ; il partage
donc avec Java certains avantages et inconvénients. Scala se distingue de Java et ressemble plus
à Python par son excellent support de l’approche de programmation dite fonctionnelle fondée sur
le principe de calcul lambda.
Le système de calcul par grappe de processeurs (cluster computing) nommé Apache Spark a été
écrit en langage Scala. Vous disposez donc avec Scala d’un excellent support des traitements
parallélisés, avec d’énormes volumes de données. Un des inconvénients de Scala est sa difficulté
de configuration, sa courbe d’apprentissage pentue et l’absence d’un jeu complet de librairies de
fonctions pour la datalogie.

Le métier le plus attirant du XXIe siècle


Par le passé, on considérait toute personne qui utilisait des statistiques comme une sorte de
comptable ou de scientifique un peu dérangé. La plupart des gens pensent que les statistiques
et les analyses de données constituent une activité ennuyeuse. Mais tout change avec la
science des données. Dorénavant, plus vous apprenez de choses, plus vous voulez en
apprendre d’autres. Lorsque vous répondrez à une question, vous en ferez souvent émerger
d’autres encore plus intéressantes. Ce qui rend la datalogie aussi attirante en tant que métier
est qu’elle pénètre tous les domaines et peut être appliquée d’une multitude de manières.
Voyons plus en détail pourquoi la datalogie constitue un champ d’études aussi prometteur.

L’émergence de la datalogie
L’expression « science des données » est assez récente puisqu’elle est apparue en 2001 sous la
plume de William S. Cleveland dans un article célèbre. Ce n’est qu’un an plus tard que le
Conseil International des Sciences a reconnu la science des données en créant un comité
spécifique. L’université de Columbia a rejoint le mouvement en 2003 en lançant la revue
technique Journal of Data Science.
N. d. T. : La palette de techniques utilisées en sciences de données correspond à des
technologies pour transformer des données en informations, un peu comme une distillation
dont on ne conserve que la partie essentielle. L’expression « science des données » étant
malcommode, nous avons songé à trouver un autre terme. Les mots donnéelogie et donnétique
ne conviennent pas (et « donnétique » est déjà utilisé pour la gestion du cycle de vie des
données et leur archivage). Le mot anglais data provient d’un mot latin qui a donné « date » en
français. Il est très connu en langue française et nous proposons donc de l’adopter. Nous
proposons donc le terme « datalogie » , une technologie centrée sur les données.
Les fondements mathématiques de la datalogie existent depuis des siècles, puisqu’il s’agit de
visualiser et d’analyser des statistiques et des probabilités. Le terme « statistique » est apparu
en 1749, mais il en existait sans doute depuis bien longtemps. C’est ainsi que l’historien
nommé Thucydide dans son histoire de la guerre du Péloponnèse avait décrit comment les
Athéniens avaient calculé la hauteur du mur de la ville de Platées au cinquième siècle avant J.-
C. en comptant le nombre de briques d’une section non enduite de la muraille. Pour plus de
certitude, ils avaient pris soin de calculer la moyenne du nombre de briques comptées par
plusieurs soldats.
Les techniques de quantification et de compréhension des statistiques sont plus récentes. Une
description de l’importance des statistiques est apparue dès le neuvième siècle ; un certain Al
Kindi en parlait dans son manuscrit sur le décryptage des messages codés. Il y montrait
comment combiner des statistiques à une analyse de fréquence pour déchiffrer des messages.
Dès leur début, les statistiques ont donc été utilisées pour résoudre des tâches apparemment
impossibles. La datalogie poursuit dans cette voie, et c’est pourquoi certains la comparent à
une sorte de magie.

Les compétences clés du datalogue


En tant que spécialiste réalisant des tâches complexes, un datalogue doit posséder une large
palette de compétences. D’ailleurs, les projets de datalogie sont en général réalisés en équipe.
Une personne très compétente pour la collecte de données coopérera avec un analyste ainsi
qu’avec une personne spécialisée dans la visualisation des informations produites. Rares sont
les individus qui peuvent se targuer d’être experts dans tous ces domaines à la fois. La liste
suivante présente les grands domaines dans lesquels un datalogue doit pouvoir briller (plus il
dispose de compétences, mieux c’est, bien sûr).
» Capture de données : rien ne sert d’être très doué en mathématiques si vous n’obtenez
pas les données sur lesquelles vous voulez appliquer ces compétences. La capture des
données commence par la maîtrise de la ou des sources de données, et donc la
connaissance des techniques de gestion de bases de données. Dans la plupart des cas,
l’obtention des données brutes ne suffit pas : vous devez être capable de comprendre ces
données pour pouvoir définir les catégories de questions à poser. Vous devez enfin
posséder des compétences en modélisation des données pour comprendre comment les
données sont interconnectées et structurées.
» Analyse : une fois que vous disposez des données et en connaissez la complexité, vous
pouvez lancer vos analyses en vous servant de votre connaissance des outils statistiques
(sans aller nécessairement au-delà d’un niveau universitaire). Cela dit, les techniques
mathématiques spécifiques et certains algorithmes permettront de faire ressortir des
motifs, et vous aideront à en tirer des conclusions, ce qui ne serait pas possible par simple
lecture des données.
» Présentation : la plupart des gens n’aiment pas trop lire des nombres, car ils ne
réussissent pas à y détecter les motifs que le datalogue sait y remarquer. Il est donc
essentiel de produire une présentation graphique de vos motifs pour aider les autres à
comprendre le sens des données et comment les appliquer de façon pertinente. Les
présentations doivent transmettre du sens pour tirer le maximum des données exploitées.

Datalogie, Big Data et IA


Vous noterez avec intérêt que les activités consistant à déplacer et préparer les données pour
pouvoir les analyser constituent un domaine à part entière, le processus ETL pour extraction,
transformation et chargement (loading). Un spécialiste ETL va se servir d’un infolangage, par
exemple Python, pour extraire des données à partir de plusieurs gisements. Cette opération
demande beaucoup de temps, car les entreprises n’ont pas pour priorité lorsqu’elles
implantent leurs données de les rendre facilement accessibles. Une fois que les données ont
été obtenues, un autre ou le même infolangage est utilisé pour transformer le format des
données afin de les rendre faciles à analyser. La troisième étape, le chargement, peut prendre
de nombreuses formes, mais dans ce livre, nous nous servons uniquement de Python. Dans un
projet réel à grande échelle, vous aurez sans doute à recourir à des outils tels qu’Informatica,
MS SSIS ou Teradata pour ce chargement.
Dans certains cas, la datalogie ne constitue qu’une étape d’un processus plus vaste. Pendant
que le datalogue progresse parmi des masses d’information, il va repérer des éléments
intéressants, éléments qui peuvent servir de points d’entrée pour d’autres analyses et
applications en intelligence artificielle (IA). Vous savez que vos habitudes d’achat permettent
aux sites marchands de vous suggérer par exemple d’autres livres ou d’autres lieux de
vacances. Vos comportements permettent de mettre en lumière d’autres activités secondaires.
Ces autres utilisations de la datalogie sont décrites dans différents livres consacrés à
l’apprentissage machine. Ce que vous allez apprendre dans ce livre pourra avoir des
répercussions sur votre carrière, dans d’autres domaines que la datalogie.

Le choix du langage de programmation


Un datalogue a souvent besoin de connaître plusieurs infolangages pour répondre à des
besoins divers. Il vous faudra par exemple connaître le langage SQL pour extraire des données
d’une base relationnelle. Pour le chargement des données, la transformation et l’analyse, vous
pouvez utiliser Python. Certains préféreront un produit tel que MATLAB qui possède son
propre langage, ou bien PowerPoint qui utilise Visual Basic pour la partie présentation. Les
énormes volumes de données que doivent gérer les datalogues obligent en général à prévoir
plusieurs couches de traitement répété pour aboutir à des données exploitables. Il n’est pas
question de réaliser manuellement ce genre de traitement, à cause de la durée et des risques
d’erreurs. La meilleure approche pour aboutir à des sources de données exploitables et
cohérentes consiste donc à programmer.
Un seul langage de programmation ne suffira pas à répondre à tous les besoins d’un datalogue
au cours de sa carrière. Le langage Python permet de charger les données, de les transformer,
de les analyser et même de générer des présentations graphiques pour les utilisateurs, mais il
ne vous satisfera que s’il offre les caractéristiques dont vos projets ont besoin. Il vous sera
donc parfois nécessaire de recourir à un autre langage. Vous choisirez en fonction d’un certain
nombre de critères dont voici les principaux :
» Le genre d’opération de datalogie prévue. Ce sera de l’analyse de données, de la
classification ou encore de la régression ?
» Votre degré de familiarité avec le langage visé ?
» Le besoin d’interagir avec un ou plusieurs autres infolangages.
» L’existence d’outils pour enrichir votre environnement de développement.
» La disponibilité d’interfaces API et de librairies de fonctions pour accélérer la
programmation.

Création du pipeline de datalogie


La datalogie est à cheval entre l’art et l’ingénierie. Font partie de sa face artistique la
reconnaissance des motifs dans les données, la formulation des questions et le choix des
algorithmes les plus adaptés. Pour que cette partie artistique puisse se concrétiser, la partie
ingénierie doit définir des processus permettant d’atteindre des objectifs clairs. Cela
correspond à un canal d’alimentation que nous appelons un pipeline. Sa mise en place suppose
de suivre une série d’étapes de préparation, d’analyse et de présentation. Cette section donne
les grandes lignes de ce pipeline de datalogie. Ce principe est appliqué dans toute la suite du
livre lors de la présentation des exemples.

Préparation des données


Les données de différentes sources que vous voulez exploiter ne se présentent pas dans un
format idéal pour les analyser, bien au contraire. Ces données brutes se présentent dans des
formats divers que vous devez mettre en cohérence. Il faudra sans doute changer les types de
certaines données, leur ordre d’apparition, voire créer de nouvelles entrées de données à
partir des entrées existantes.

Analyse exploratoire des données


L’analyse des données s’appuie sur des principes d’ingénierie qui garantissent que les résultats
peuvent être prouvés et restent cohérents. La datalogie permet d’utiliser une foule de
méthodes statistiques et autres algorithmes qui vous servent à détecter des motifs
remarquables dans les données. En général, une seule approche ne suffit pas. Préparez-vous à
un processus répétitif pour retravailler les données selon plusieurs approches. L’art de la
datalogie intègre une approche par essais et erreurs.

Apprendre des données


En appliquant des méthodes d’analyse statistique pour détecter des motifs, vous allez
commencer à apprendre des choses à propos des données. Ce ne sera pas toujours ce à quoi
vous vous attendiez, et parfois les données vont mettre en lumière plusieurs histoires. La
découverte fait partie du métier de datalogue, et c’est d’ailleurs une partie agréable, car vous
ne saurez jamais d’avance exactement ce que l’analyse des données va révéler.
Le flou relatif qui entoure les données et la détection de motifs apparemment erratiques va
vous obliger à garder toujours l’esprit ouvert. Vous ne trouverez pas les informations que vos
données peuvent produire si vous restez bloqué sur ce que vous aviez cru devoir y trouver.
Cela vous empêche de goûter la phase de découverte, et vous fait manquer des opportunités,
tant pour vous que pour ceux qui attendent le fruit de vos recherches.

Visualisation
La phase de visualisation consiste à rendre visibles les motifs remarquables dans les données
afin de pouvoir prendre des décisions. Cela suppose de pouvoir distinguer les données qui ne
font pas partie du motif. Vous pouvez endosser le rôle d’un sculpteur de données qui supprime
les données non pertinentes, aberrantes, pour que les autres puissent admirer la superbe
statue d’informations qui était cachée à l’intérieur. Mais pour que cette œuvre ne soit pas
visible qu’à vos yeux, il faut prévoir de faire un effort de représentation.

Valorisation des données produites


Le datalogue semble pouvoir arrêter son travail lorsqu’il a trouvé des façons uniques de
représenter les données. Mais il s’agit d’aller encore plus loin en obtenant une compréhension
détaillée de ce que ces données signifient. Les éclaircissements que procure l’analyse doivent
vous permettre de déclencher des actions dans le monde réel. Vous pouvez par exemple vous
servir des résultats d’une analyse pour prendre une décision d’entreprise.
Dans certains cas, les résultats sont immédiatement exploités sous forme d’une réponse
automatique. Par exemple, lorsqu’un robot analyse les pixels d’une caméra, le résultat permet
à ce robot de prendre des décisions d’action par rapport à un objet ainsi détecté. Mais pour
que le robot y voie quelque chose, il faut que le datalogue ait d’abord créé une application pour
charger, analyser et visualiser ce que collecte la caméra.

Rôle de Python en datalogie


Si vos sources de données, vos besoins d’analyse et ceux de présentation conviennent, vous
pouvez utiliser le langage Python pour la totalité des étapes de la chaîne de traitement de
datalogie. C’est ce que nous allons faire dans ce livre. Tous les exemples utilisent Python pour
décrire une des activités concernées. Parmi tous les langages utilisables dans ce domaine,
Python est le plus polyvalent et le plus efficace grâce aux très nombreuses librairies de
fonctions disponibles dans ce domaine. Dans cette section, nous allons voir pourquoi Python
est un excellent choix pour répondre à la plupart des besoins de traitement de datalogie.

Le profit hybride du datalogue


L’expert en sciences des données, ou datalogue, est souvent vu comme une sorte de sorcier
inaccessible qui réalise des miracles avec des formules mathématiques. Cette image féerique
est en train de s’adoucir. De plus en plus souvent, le datalogue est considéré soit comme un
collègue du programmeur, soit comme un nouveau style de programmeur. Cette évolution est
la conséquence de l’apparition d’applications capables d’apprendre. Pour qu’une application
puisse apprendre, elle doit pouvoir traiter d’énormes bases de données pour y découvrir des
motifs remarquables. Elle doit ensuite générer de nouvelles données à partir des données
d’entrée, ce qui pourrait ressembler à des prédictions. Ce nouveau genre d’application
commence à avoir un impact inimaginable il y a encore quelques années. Le domaine dans
lequel ces applications sont les plus visibles de nos jours est celui des robots qui vont parvenir
à interagir bien plus finement avec les êtres humains.
En termes économiques, la convergence entre science des données et programmation
d’applications est évidente : les entreprises ont besoin de réaliser toutes sortes d’analyse avec
les données qu’elles collectent, pour obtenir des informations afin d’émettre des hypothèses.
Mais un impact encore plus important de cette convergence sera l’apparition d’applications
d’un tout nouveau genre, que l’on n’imagine que difficilement de nos jours. Un programme
pourrait par exemple aider les étudiants à optimiser leur apprentissage en analysant leurs
comportements actuels pour générer de nouvelles méthodes pédagogiques mieux adaptées à
chacun d’eux. Le même genre d’analyse permettra de résoudre des problèmes médicaux
actuellement hors de portée pour réduire les maladies autant que pour produire des appareils
et prothèses de haute qualité.

Un langage simple, efficace et polyvalent


Comme déjà dit, ce livre va présenter l’une des méthodes disponibles pour expérimenter la
datalogie. Le langage Python constitue une solution unique pour résoudre des problèmes
complexes. Avec le même langage, Python, vous allez pouvoir réaliser les différentes tâches qui
normalement réclament plusieurs outils différents, car vous allez enrichir Python en fonction
de vos besoins grâce aux librairies de fonctions.
Les librairies de fonctions disponibles pour le langage Python constituent un de ses points
forts, mais ce n’est pas le seul. En effet, Python permet d’adopter quatre styles de
programmation différents :

» Programmation fonctionnelle : chaque instruction est une équation mathématique, et


le changement de valeur des données est évité. Le grand avantage de cette approche est
de n’avoir aucun effet secondaire. Ce style s’adapte mieux que d’autres aux traitements
parallèles, puisqu’il n’y a pas d’état intermédiaire à gérer. Les programmeurs privilégient
ce style de codage d’abord pour les parties récursives et pour les calculs lambda.
» Programmation impérative : les calculs et traitements modifient l’état du programme.
Ce style est apprécié pour l’exploitation des structures de données. Le code source produit
est élégant et reste simple.
» Programmation orientée objet : les données correspondent à des champs qui
appartiennent à des objets et qui ne peuvent être modifiés que par des méthodes de ces
objets. Python ne supporte pas totalement cette approche, car il ne permet pas
l’encapsulation des données. Ce style d’écriture est très intéressant pour les applications
sophistiquées grâce aux deux mécanismes d’encapsulation et de polymorphisme. Ce style
favorise en outre la réutilisation du code.
» Programmation procédurale : chaque traitement est décomposé en étapes et les
opérations répétitives sont placées dans des fonctions qui sont appelées. Ce style favorise
la répétition, l’exécution de séquences, la sélection et la modularité.

Découverte de l’atelier Python


Comme promis, découvrons maintenant brièvement comment mettre en action le pipeline de
datalogie avec le langage Python. Nous verrons en détail les différentes étapes dans les
chapitres suivants. Vous n’êtes pas supposé pratiquer la suite de cette section. En effet, nous
ne verrons comment installer Python que dans le Chapitre 3. Ici, notre objectif est de donner
un premier ressenti sur la façon dont vous pouvez faire de la datalogie avec Python. Ne vous
inquiétez pas si vous ne comprenez pas un des termes utilisés dans cette rapide visite guidée.
Tout va s’éclaircir au fur et à mesure de votre progression dans le livre.
La plupart des exemples du livre montrent une application fonctionnant dans votre navigateur
Web et portant le nom Jupyter Notebook. Les captures d’écran montrent l’aspect de cet outil
dans le navigateur sous Windows. Vous remarquerez sans doute de petites différences si vous
utilisez un autre système d’exploitation ou un autre navigateur Web, mais rien de gênant.

Chargement des données


Pour pouvoir exploiter des données, il faut d’abord les avoir à disposition. Nous verrons
plusieurs techniques pour charger les données. Dans la Figure 1.1, nous chargeons un jeu de
données portant le nom Boston et contenant les prix de vente des maisons dans la région de
Boston. La deuxième ligne de code source stocke la totalité de ce jeu de données dans la
variable portant le nom boston puis stocke un sous-ensemble de ces données dans les deux
variables X et y. Une variable est une boîte dans laquelle vous pouvez stocker une valeur. C’est
grâce à ces variables que vous pouvez effectuer des traitements sur les données.

Figure 1.1 : Données chargées dans des variables pour pouvoir les manipuler.

Application d’un modèle de traitement


Une fois que vous avez rassemblé des données, vous allez pouvoir leur appliquer un
traitement, choisi parmi tous ceux proposés dans Python. La Figure 1.2 montre par exemple
l’application d’un modèle de régression linéaire. Ne vous inquiétez par des détails du
fonctionnement, car nous les verrons dans les prochains chapitres. Ce qu’il faut retenir de la
Figure 1.2 est que grâce à Python, vous pouvez lancer une régression linéaire au moyen de
seulement deux instructions et faire stocker le résultat dans une variable qui s’appelle ici
hypothesis.

Figure 1.2 : Le contenu d’une variable sert à entraîner un modèle de régression linéaire.

Visualisation des résultats


Quelle que soit la qualité d’une analyse, il est important de pouvoir rendre accessibles ses
résultats. Nous verrons de nombreuses techniques de création d’une représentation dans ce
livre. La Figure 1.3 donne un premier exemple qui montre les coefficients produits par notre
analyse de régression linéaire.

Figure 1.3 : Production du résultat sous forme de coefficients à partir du modèle.

Une des raisons pour lesquelles nous avons choisi l’outil Jupyter Notebook dans ce livre est
qu’il permet de créer facilement des représentations visuelles joliment présentées directement
dans l’application. Si vous revenez à la Figure 1.3, vous voyez que la série de valeurs peut être
directement réutilisée par un collègue statisticien. Bien sûr, tout le monde ne peut pas
directement comprendre ce genre de données, mais ceux qui connaissent les statistiques et la
datalogie y trouveront déjà de quoi établir des hypothèses et détecter des tendances.
Chapitre 2
Capacités du langage Python
DANS CE CHAPITRE :
» Origines de Python

» Un rapide tour d’horizon

» Caractéristiques spéciales de Python

» Avantages de Python pour la datalogie

L es ordinateurs ne connaissent qu’un langage : le code machine. À moins d’avoir le temps


d’apprendre à parler comme un ordinateur, uniquement avec des zéros et des un, vous
n’utiliserez pas le code machine. (Vous ne risquez pas de réussir à créer un projet de
datalogie ainsi, car cela vous prendrait plus d’une vie entière.) Il existe heureusement des
langages de plus haut niveau, qui sont beaucoup plus faciles à lire et à écrire par des êtres
humains. Des outils logiciels sont prévus pour convertir un texte source écrit dans un de ces
langages vers le code machine que l’ordinateur peut exécuter. Tous les langages informatiques
sont ainsi convertis en langage machine ; c’est donc à vous de choisir le langage qui convient à
votre projet. Ce chapitre va vous montrer pourquoi Python est particulièrement adapté à tous
les projets de datalogie. Certains d’entre vous veulent d’abord savoir pourquoi nous avons
choisi ce langage et non le Java ou le C++. Ces autres langages sont tout à fait adaptés à
certains domaines, mais ils le sont un peu moins au domaine qui nous intéresse ici.
Commençons par un bref historique du langage Python, afin que vous sachiez d’où il vient.
Nous verrons quelques exemples de code source Python très simples pour avoir un premier
avant-goût, puis nous découvrirons quelques caractéristiques spécifiques du langage Python,
avec notamment un tour d’horizon des librairies de fonctions qui sont vraiment indispensables
à la datalogie. Nous utiliserons plusieurs de ces librairies pour réaliser les exemples du livre.
En connaissant leurs noms dès à présent, vous tirerez plus facilement profit des exemples, et
comprendrez pourquoi le livre propose de résoudre les problèmes comme il le fait.
Ce chapitre contient des exemples en Python, mais vous n’allez commencer à pratiquer le
codage Python qu’à partir du Chapitre 6. Pour l’instant, nous découvrons la nature du langage
Python. Dans le prochain chapitre, nous montrerons comment installer la version de Python
utilisée dans ce livre. Les Chapitres 4 et 5 présentent les outils. Autrement dit, ne vous
inquiétez pas si vous ne comprenez pas tous les exemples de ce chapitre, car nous reviendrons
sur ces éléments dans les chapitres suivants.

Pourquoi Python ?
Au départ, Python est l’œuvre d’une seule personne, le Néerlandais Guido van Rossum. Python
n’est pas vraiment une nouveauté : Guido avait commencé à travailler en décembre 1989 à un
remplacement du langage ABC qui ne lui suffisait pas. Les objectifs exacts qu’il recherchait ne
sont pas tous connus, mais l’un des principaux était de maintenir une compatibilité avec le
langage ABC tout en demandant d’écrire moins de lignes. Le résultat est bien supérieur à ABC
et permet de créer des applications de toutes sortes dans toutes sortes de domaines. Mieux
encore, Python permet d’adopter quatre styles de programmation différents. Bref, Guido est
parti du langage ABC, en a déterminé les limitations et a créé un nouveau langage qui en était
exempt. C’est un bon exemple d’amélioration largement supérieure à son prédécesseur.
Python a beaucoup évolué et offre actuellement deux lignées distinctes. La lignée 2.x maintient
la compatibilité avec les anciennes versions de Python, ce qui n’est pas le cas de la lignée 3.x
qui lui succède. Ce problème de compatibilité doit être pris en compte, y compris en datalogie,
car certaines des librairies requises ne fonctionnent pas encore avec la lignée 3.x, mais cette
contrainte disparaît progressivement. Vous êtes convié à utiliser la lignée 3.x pour tous vos
nouveaux projets parce que la lignée 2.x va finir par être abandonnée (voyez
https://pythonclock.org/ pour tous détails). Ce livre n’utilise que du code pour la lignée 3.x.
Du fait qu’au cours de sa carrière Guido a travaillé pour plusieurs entreprises, les licences des
différentes versions de Python divergent. Cependant, la fondation PSF (Python Software
Foundation) est dorénavant propriétaire des droits de toutes les versions de Python. Vous
n’avez donc pas à vous inquiéter au niveau des droits d’utilisation du langage (sauf si vous
utilisez encore une très ancienne version).

CHOISIR LE BON LANGAGE

Les infolangages servent à écrire des instructions de traitement de données. L’ordinateur, comme
déjà dit, ne connaît que les zéros et les un du langage binaire. Python est particulièrement bien
adapté aux projets de datalogie, mais il ne convient pas à tous les domaines. Pour chaque tâche à
réaliser, vous devez choisir le langage qui vous semble plus approprié. Dès qu’un langage semble
devenir un obstacle, il faut voir si un autre ne pourrait pas mieux convenir. Les infolangages sont là
pour rendre service aux humains, pas aux ordinateurs.

Philosophie de Python
Guido van Rossum s’était lancé dans la création de Python à ses heures perdues. Il voulait
obtenir un résultat le plus vite possible tout en créant un langage souple, fonctionnant sur
toutes les plates-formes et acceptant des extensions. Python dispose de toutes ces qualités, et
de bien d’autres. Bien sûr, il a fallu faire des choix, par exemple au niveau de la facilité d’accès
aux rouages internes du système. Vous trouverez un historique de Python sur la page wiki
correspondante. Les anglophones pourraient également consulter le site http://python-
history.blogspot.com/2009/01/introduction-and-overview.html.

Graphes Python et datalogie


Puisque ce livre est consacré à la datalogie, il est indispensable d’expliquer en quoi le langage
Python permet de mieux pratiquer cette science. Il ne suffit pas de savoir qu’un très grand
nombre d’entreprises et d’instituts de recherche utilisent Python, car cela ne nous permet pas
de savoir comment ils s’en servent.
Un excellent exemple du processus ayant permis de sélectionner Python pour la datalogie est
fourni par le site de prévisions météorologiques américain https://forecastwatch.com/. Ce
site compare 36 000 prévisions avec la situation réelle, puis se sert des résultats pour affiner
ses propres prévisions. Le travail consiste à fusionner les données météo de plus de 800 villes
aux USA, ce qui n’est pas une mince affaire. Le site avait donc besoin d’un langage qui
répondait à plusieurs contraintes. Voici les raisons qui ont poussé forecastwatch.com à choisir
le langage Python :
» Support des librairies de fonctions : le langage Python est accompagné d’un vaste
nombre de librairies, largement plus qu’il n’en faut pour chacun des domaines pressentis.
Le site de prévisions météo a notamment besoin d’utiliser les librairies pour les
expressions régulières, pour le fonctionnement en parallèle par exétrons, pour la
sérialisation des objets dans des fichiers et pour la compression des données par l’outil
gzip.
» Traitements parallèles : chacune des prévisions météo est générée par un exétron
distinct (un fil d’exécution). Les données de l’exétron englobent l’adresse URL de la page
Web qui va contenir la prévision ainsi que des informations de catégorie, et notamment le
nom de la ville concernée.
» Accès aux données : l’énorme volume de données à traiter ne peut pas être stocké en
mémoire vive. Le site se fonde sur une base de données MySQL grâce à la librairie MySQL
DB. (N.d.T. : au moment d’écrire ces lignes, cette librairie d’accès à une base MySQL a été
portée vers la lignée Python 3.x.)
» Visualisation des données : au départ, le site forecastwatch.com utilisait le langage
PHP pour ses sorties. Par l’adoption de l’extension de visualisation nommée Quixote
(https://www.mems-exchange.org/software/quixote/), le site peut dorénavant
exploiter directement les données générées par Python.

Présent et futur de Python


De nos jours, le langage Python a largement dépassé les objectifs que Guido s’était donnés au
départ. Il voulait disposer d’un second langage pour créer de petits programmes utilitaires,
avec plus de possibilités qu’un langage de script. Les programmeurs visés étaient au départ
ceux du langage C.
Il existe dorénavant une quantité énorme d’applications écrites en Python, et ce n’est plus du
tout un langage réservé à l’écriture de petits scripts d’automatisation. Au moment d’écrire ce
livre, Python est le quatrième langage le plus utilisé dans le monde selon l’index TIOBE. Son
succès ne risque pas de s’étioler, car nombreux sont les programmeurs qui le trouvent
accessible tout en étant tout à fait adapté aux applications modernes, notamment en datalogie.
Vous pouvez jeter un œil sur les listes d’application créées en Python dans les pages
https://www.python.org/about/apps/ et https://www.python.org/about/success/.
La réussite du langage a un effet boule de neige et nombreux sont ceux qui veulent apporter
leur pierre à l’amélioration du langage. Il existe ainsi des listes de propositions
d’enrichissement Python (PEP) à l’adresse http://legacy.python. org/dev/peps/. Toutes les
propositions ne verront pas le jour, mais ce foisonnement prouve que Python est un langage
vivant qui va continuer à apporter de nouvelles caractéristiques vraiment utiles. Les
programmeurs veulent créer des applications dans tous les domaines, et pas seulement dans
celui de la datalogie.

Aperçu de l’utilisation de Python


Comme déjà dit, ce livre n’est pas destiné à vous guider dans vos premiers pas en
programmation Python. Vous trouverez un livre sur ce sujet dans la même collection (Python
pour les Nuls). Contentons-nous d’un rapide tour d’horizon du langage pour voir comment vous
allez l’utiliser.
Vous n’avez pas besoin de réaliser les exercices qui suivent. Nous verrons d’abord dans le
prochain chapitre comment installer votre environnement de travail.

Un avant-goût du langage
Python a été conçu en sorte de rester facile à lire et relire tout en étant rapide à écrire. Une
seule ligne de code source Python permet de déclencher un volume de traitement pour lequel il
faut plusieurs lignes dans d’autres langages. Voici par exemple comment demander à Python
de faire afficher un petit message à l’écran :

print("Bonjour Terrien !")

Il s’agit ici d’un appel de fonction valable pour la lignée 3.x. En version 2.x, le programmeur
utilisait une instruction nommée print (sans paire de parenthèses). Dans la lignée 3.x de
Python, si vous écrivez print sans les parenthèses, vous serez sanctionné par un message
d’erreur :

File “<Jupyter-input-1-fe18535d9681>”, line 1


print “Bonjour Terrien !”
^
SyntaxError: Missing parentheses in call to ‘print’. Did you
mean print(“Bonjour Terrien !”)?

Insérer cette simple fonction permet d’afficher aussi bien du texte que le contenu d’un objet ou
des valeurs numériques, avec une seule instruction. Pour quitter cette session interactive sur
ligne de commande, il suffit de saisir le nom de fonction quit(), puis de valider par la touche
Entrée. Dans ce livre, nous utiliserons un véritable environnement de travail qui porte le nom
Jupyter Notebook. Il permet réellement de charger l’équivalent d’un calepin de travail (un
notebook) dans lequel se trouve le code source.

De l’importance de l’indentation
Le langage Python se distingue de la plupart de ses concurrents par le fait que le nombre
d’espaces en début de chaque ligne joue un rôle sur la logique des programmes, ce qu’on
appelle l’indentation. Les débutants en Python font souvent l’erreur de ne pas indenter
suffisamment les lignes qui doivent être des sous-ensembles d’autres lignes. Nous verrons cela
en pratique plus loin. Sachez dès à présent qu’il faut bien faire attention à l’alignement des
premiers caractères au niveau des colonnes. Par exemple, dans un groupe de lignes qui doivent
être exécutées seulement dans certaines conditions, c’est-à-dire un bloc conditionnel if, les
lignes conditionnelles doivent toutes être décalées vers la droite de deux ou quatre espaces ou
d’un pas de tabulation. Voici un exemple :
print("Indentons bien !")
if 1 < 2:
print("1 vaut moins que 2")

Ci-dessus, la seconde fonction print() doit être décalée par rapport à la ligne précédente
pour que cette instruction d’affichage ne soit exécutée que si la condition testée est satisfaite,
autrement dit, si la valeur 1 est inférieure à la valeur 2 (ce qui est normalement le cas dans
notre univers). En cas d’erreur, vous verrez apparaître un message du compilateur.

ANACONDA EST UN PAQUETAGE

Dans ce livre, nous considérons Anaconda comme un produit en soi, avec lequel vous allez travailler.
En réalité, Anaconda est le fruit du regroupement de plusieurs applications open source. Vous pouvez
vous servir de chacune d’elles de façon individuelle dans certains cas. Dans la plupart des chapitres
du livre, nous utilisons l’éditeur Jupyter Notebook, mais il n’est pas inutile de savoir qu’il y a d’autres
applications regroupées sous le terme Anaconda.

La plupart des datalogues se servent aussi d’Anaconda en tant que produit global, mais
certains des logiciels regroupés dans ce paquetage peuvent avoir fait l’objet de mises à jour
plus récemment que le dernier regroupement en date. Par exemple, Jupyter Notebook est
disponible dans une version plus récente que celle du paquetage Anaconda
(http://jupyter.org/). Pour éviter tout souci, vous n’utiliserez pour les exercices de ce livre
que la version de Jupyter Notebook qui est fournie avec la version la plus récente du paquetage
Anaconda, et non la version disponible séparément.

Ligne de commande ou environnement intégré ?


Anaconda est un véritable environnement qui rend l’utilisation du langage Python encore plus
simple. Parmi les nombreux outils regroupés dans Anaconda, nous utiliserons quasiment
toujours Jupyter Notebook que nous allons installer dans le Chapitre 3. Nous n’utiliserons
pratiquement pas les autres outils disponibles dans Anaconda. Passons-les néanmoins en revue
pour avoir un aperçu global de ce qu’il est possible de faire avec Anaconda. Vous pourrez aussi
les découvrir au fur et à mesure des travaux réalisés tout au long du livre.

L’invite de commande Anaconda Prompt


Un des outils de la panoplie Anaconda permet d’accéder directement à une ligne de
commande ; il se nomme Anaconda Prompt. La fenêtre correspondante est en mode texte et
permet de saisir des commandes. L’intérêt est de pouvoir lancer Anaconda en ajoutant des
options pour modifier l’environnement standard de l’outil que vous voulez démarrer. En temps
normal, vous allez démarrer les utilitaires dont vous avez besoin depuis l’interpréteur Python
qui correspond à python.exe.
Si vous avez installé simultanément la lignée Python 3.x et la lignée Python 2.x sur votre
machine, il est possible que ce soit la 2.x qui démarre. Il vaut toujours mieux ouvrir une ligne
de commande Anaconda comme indiqué ici pour disposer de la bonne version de Python.

Pour démarrer l’interpréteur Python directement, vous pouvez donc saisir comme commande
dans cette fenêtre le mot python, puis valider par Entrée, ce qui donne le résultat montré dans
la Figure 2.1.

Figure 2.1 : Aspect de l’interpréteur Python sur ligne de commande.

Pour quitter l’interpréteur, vous utilisez la commande quit() en validant par Entrée (nous ne
le préciserons plus). Pour un rappel des options de ligne de commande de l’interpréteur,
saisissez la commande python - ? . La Figure 2.2 montre quelques-unes des options
disponibles pour influer sur l’environnement de travail.
Figure 2.2 : Rappel des options de ligne de commande de l’interpréteur Python.

En faisant exécuter un script au démarrage de l’interpréteur, vous pouvez utiliser une version
personnalisée de l’utilitaire concerné. Tous les scripts sont stockés dans le sous-répertoire
nommé scripts. Par exemple, si vous saisissez la commande suivante :

python Anaconda3/scripts/Jupyter-script.py

vous démarrez l’environnement Jupyter sans avoir besoin de passer par l’interface graphique
de votre système. Vous pouvez même spécifier d’autres options pour modifier le script lui-
même. Voici quelques paramètres que vous pouvez utiliser avec le script pour obtenir des
informations au sujet de Jupyter :
» --version : affiche le numéro de version de Jupyter Notebook.
» --config-dir : affiche le répertoire de configuration de l’outil Jupyter Notebook qui
contient les informations de configuration.
» --data-dir : affiche le répertoire contenant les données d’application Jupyter et non
celui des projets. En général, les projets sont stockés dans votre répertoire d’utilisateur.
» --runtime-dir : affiche le nom du répertoire contenant les fichiers d’exécution de
Jupyter, qui est normalement un sous-répertoire du répertoire data.
» --paths : affiche la liste des chemins d’accès que Jupyter doit consulter.

Pour obtenir la liste des chemins d’accès Jupyter, il suffit donc de taper la commande suivante :

python Anaconda3/scripts/Jupyter-script.py --paths

Vous trouverez toute une série de fichiers exécutables dans le même sous-répertoire scripts,
qui sont des versions compilées de certains scripts pour permettre une exécution plus rapide.
Pour démarrer l’environnement du navigateur Jupyter Notebook, saisissez la commande
suivante :

python Anaconda3/scripts/Jupyter-notebook-script.py

La commande Jupyter-notebook.exe donne le même résultat.

L’environnement interactif IPython


L’outil nommé IPython (Interactif Python) propose quelques améliorations par rapport à
l’interpréteur Python standard. Vous entrez dans cet environnement au moyen de la commande
IPython en remplacement de la commande python, depuis la fenêtre d’invite Anaconda
Prompt. La Figure 2.3 montre la fenêtre correspondante. La version de Python est strictement
la même qu’avec la commande standard. Au moyen de la commande %quickref suivie de la
touche Entrée, vous pouvez obtenir un aperçu des enrichissements apportés par cette variante
de l’interpréteur.
Un des ajouts intéressants de IPython semble mineur, mais il est très pratique : c’est sa
commande cls qui permet d’effacer le contenu de la fenêtre, ce qui n’est pas facile à obtenir
avec l’interpréteur Python standard. Vous pouvez également faire rechercher des noms de
variables avec des caractères génériques et profiter de quelques fonctions magiques pour par
exemple chronométrer la durée d’exécution d’une tâche, comme nous le verrons dans un
chapitre ultérieur.

Figure 2.3 : L’environnement IPython qui apporte quelques enrichissements par rapport à l’interpréteur Python standard.

L’environnement Jupyter QTConsole


Il n’est pas facile de mémoriser toutes les commandes des fonctions du langage Python, sans
compter les commandes ajoutées par l’outil Jupyter. Certaines personnes n’aiment pas du tout
travailler à partir de mots. Ils seront heureux de découvrir l’outil Jupyter QTConsole qui
propose une interface utilisateur graphique pour naviguer parmi les commandes de Jupyter de
façon bien plus confortable.
Dans les récentes versions d’Anaconda, vous pourriez croire que QT console n’est plus
disponible, mais ce n’est que l’accès direct qui a disparu. Pour démarrer la fenêtre de
QTConsole, ouvrez une invite de commande Anaconda Prompt et saisissez la commande
Jupyter QTConsole puis validez par Entrée. Vous devriez voir une fenêtre comme celle de la
Figure 2.4. Bien sûr, les programmeurs traditionnels préfèrent travailler en mode texte pur,
plutôt que dans une interface semi-graphique. Vous choisirez en fonction de vos préférences.
Un certain nombre de commandes sont disponibles dans les différents menus du haut de la
fenêtre. Par exemple, pour redémarrer le noyau, vous utilisez la commande de menu
Kernel/Restart current kernel. Vous avez accès à toutes les commandes disponibles dans
IPython. Vous pouvez ainsi saisir %magic puis Entrée pour obtenir la liste des commandes
magiques.

Figure 2.4 : L’outil QTConsole permet de contrôler Jupyter dans une interface graphique.

L’éditeur de script Spyder


L’outil Spyder est un véritable environnement de développement intégré (EDI) aussi appelé
atelier de développement. Depuis cet atelier, vous pouvez rédiger des scripts, les charger, les
modifier, en lancer l’exécution et les mettre au point. La Figure 2.5 en montre l’aspect général.
Figure 2.5 : Aspect général de l’environnement de développement Spyder.

Cet environnement fonctionne sur le même principe que tous les environnements de
développement existants. La partie gauche est réservée à un éditeur pour le code source. Ce
code est sauvegardé dans un fichier script. Il faut donc enregistrer au moins une fois avant de
lancer l’exécution. Dans la partie droite, le volet supérieur propose deux onglets pour inspecter
les objets du programme, explorer les variables et interagir avec les fichiers. Le volet inférieur
droit est celui de la console Python qui donne également accès à un historique des messages et
à la console Jupyter. La partie supérieure correspond à la barre de menus qui permet de piloter
votre environnement.

Prototypage rapide et expérimentation


Une des promesses de Python consiste à permettre de créer une application rapidement en
progressant par tests successifs. La création d’une application sans avoir peaufiné la partie
théorique au préalable correspond à cette approche de prototypage. Vous aurez moins de
lignes de code à écrire en Python qu’avec un autre langage, ce qui accélère le prototypage. La
plupart des traitements dont vous aurez besoin sont directement disponibles en tant que
fonctions d’une librairie ou d’une autre. Il suffit donc de rendre ces fonctions accessibles par
un import pour arriver plus vite à vos fins.
Il n’existe pas de solution unique en datalogie et vous devrez souvent tenter plusieurs
approches différentes. C’est à ce titre que l’expérimentation est indispensable. Dès que vous
avez créé un premier prototype, vous vous en servez avec différents algorithmes pour choisir
celui qui fonctionne mieux. L’algorithme sélectionné variera en fonction des réponses que vous
obtenez et des données d’entrée. Le nombre de variables est tel qu’aucune solution prédéfinie
n’est envisageable.
Plusieurs phases sont à considérer pour votre prototypage et votre expérimentation. Au cours
de la lecture du livre, vous verrez que les différentes phases se présentent toujours dans un
ordre particulier. Voici ces grandes phases, que vous allez normalement parcourir dans chaque
projet :
1. Construction du pipeline de données. Pour analyser des données, il faut d’abord
établir un canal d’alimentation. Lorsque le volume de données dépasse les possibilités de
l’espace en mémoire vive (RAM), vous devrez travailler avec des données situées dans des
fichiers sur disque ou en réseau. Les techniques d’accès aux données ont un impact direct
sur les performances de vos traitements.
2. Reformatage des données d’entrée. Le format des données, c’est-à-dire ses
caractéristiques et ses types, sont à préciser. Vous ne pouvez lancer de comparaison que
sur des données comparables. Le formatage doit en outre être approprié aux algorithmes
que vous allez appliquer à vos données. Nous verrons à partir du Chapitre 7 à quel point il
est indispensable de savoir reformater les données de différentes façons.
3. Analyse des données. Comme déjà dit, vous utiliserez en général plusieurs algorithmes
sur les mêmes données, jusqu’à savoir lequel donne les meilleurs résultats.
4. Présentation des résultats. On dit qu’une image vaut un long discours. Mais il faut que
cette image véhicule le sens que vous avez recueilli par votre analyse. Vous pouvez par
exemple utiliser les fonctions de tracé de graphique de style MATLAB (librairie matplotlib)
pour générer plusieurs visualisations des mêmes données, avec une approche différente
pour chacune. En testant plusieurs méthodes de visualisation, vous pourrez garantir que la
signification de vos données n’est pas perdue en chemin.
Considérations de performances
Les ordinateurs sont capables de calculer à des vitesses phénoménales, mais vos analyses vont
réclamer une grande puissance de traitement, car les volumes de données sont généralement
énormes. Voici les principaux critères qui vont déterminer les performances de vos applications
de datalogie :
» Taille du jeu de données : le principe même de la datalogie est d’exploiter d’énormes
volumes de données. Il n’est pas impossible de rendre un robot capable de détecter son
environnement avec un petit jeu de données, mais lorsqu’il s’agit d’aider à une décision
d’entreprise, plus les données sont volumineuses, mieux ce sera. C’est le type
d’application qui va déterminer la taille du jeu de données ; il est indispensable d’estimer
de façon précise le volume à traiter, notamment si le projet doit fonctionner en temps réel,
par exemple dans le cas du pilotage d’une voiture autonome.
» Techniques de chargement des données : cherchez systématiquement à disposer des
meilleures performances de chargement des données, quitte à procéder au remplacement
du matériel actuel. L’accès à des données en mémoire vive est toujours bien plus rapide
que sur disque. L’accès à des données locales est normalement plus rapide que via un
réseau. La situation la moins favorable est celle dans laquelle les données ne sont
disponibles que via une connexion Internet. Nous découvrirons les techniques
d’alimentation en données dans le Chapitre 6.
» Style d’écriture : certains prétendent qu’il n’est pratiquement pas possible d’écrire une
application trop peu performante en Python. C’est faux. Il est toujours hélas possible de
mal écrire le code source d’une application, en n’utilisant pas correctement les éléments
du langage. Pour obtenir une application performante en datalogie, il faut adopter les
meilleures techniques de codage, en commençant par celles que vous découvrirez tout au
long de ce livre.
» Performances matérielles : n’espérez pas être satisfait d’une application de traitement
de données sur une machine lente avec peu de mémoire. Vous devez disposer du meilleur
matériel qui vous sera accessible. Les applications de datalogie sont gourmandes en
puissance de traitement et en volume disque. N’espérez pas faire d’économies.
» Algorithmes d’analyse : les résultats en termes de qualité et de performances vont
dépendre des algorithmes que vous adoptez. Dans les chapitres qui suivent, vous
découvrirez plusieurs méthodes pour atteindre le même but avec différents algorithmes.
Vous devez cependant faire des tests avec vos jeux de données spécifiques pour
déterminer l’algorithme qui leur convient le mieux.

Les performances et l’exactitude sont deux critères majeurs en datalogie, mais ils sont
divergents : vos décisions doivent les combiner de la façon la plus adéquate à chaque projet.

Capacités de visualisation
Avec Python, vous pouvez explorer l’environnement de données sans avoir à utiliser un outil de
mise au point, comme ce serait le cas avec des langages compilés. Par exemple, la fonction
print() permet de voir le contenu de tous les objets de façon interactive. Vous pouvez donc
charger des données puis jouer avec. En observant vos données sous plusieurs angles, vous
allez trouver de nouvelles approches. À en juger d’après les discussions en ligne, cette
expérimentation avec les données est ce qui rend la pratique de la datalogie particulièrement
agréable.
Parmi les outils réunis dans Anaconda pour jouer avec les données, un des plus intéressants est
l’environnement interactif IPython que nous venons de présenter. Tout ce que vous faites dans
cet environnement est temporaire : vous ne risquez donc pas d’endommager les données. Vous
pouvez donc charger un jeu de données juste pour voir ce qu’il contient, comme le montre la
Figure 2.6. Ne vous inquiétez pas de la signification du contenu de cette fenêtre pour l’instant.
Nous allons commencer à programmer à partir du Chapitre 5.
Figure 2.6 : Exemple de chargement d’un jeu de données pour expérimentation.

Les jeux de données au format Scikit-learn se présentent sous forme de bancs, qui sont des
structures de données. Un tel jeu de données est structuré au moyen de fonctions dans le code,
et ce code définit des clés qui identifient des valeurs, ces valeurs correspondant à des colonnes
de données. Chaque ligne de données correspond à une clé unique, même si les valeurs des
colonnes se répètent dans les lignes. Le jeu de fonctions correspondantes permet de traiter le
jeu de données en fonction des besoins de l’application.
Avant de pouvoir manipuler un jeu de données, il faut pouvoir le rendre accessible dans votre
environnement local. La Figure 2.7 montre un processus d’importation qui utilise la fonction
keys() pour afficher la liste des clés qui permettent ensuite d’accéder aux données du jeu.

Figure 2.7 : Utilisation d’une fonction pour connaître les clés d’un jeu.

Une fois que vous possédez la liste des clés, vous pouvez accéder aux différents éléments de
données. La Figure 2.8 montre tous les noms des caractéristiques d’un jeu de données nommé
Boston. Grâce à Python, vous pouvez récolter suffisamment d’informations au sujet d’un jeu
pour pouvoir vous lancer dans son analyse en profondeur.

Figure 2.8 : Accès à des données grâce à une clé.

L’écosystème Python et la datalogie


Nous avons déjà expliqué qu’il fallait charger des librairies de fonctions pour pouvoir réaliser
des traitements de datalogie en Python. Passons en revue les librairies que nous utiliserons
dans les différents exemples du livre.

Fonctions scientifiques avec SciPy


Le nom SciPy correspond à un recueil de plusieurs librairies (http://www.scipy.org/) qui
regroupent des fonctions pour les mathématiques, les sciences et l’ingénierie. Le simple fait
d’installer SciPy vous procure un vaste jeu de librairies permettant de créer des applications
très diverses. Voici les noms des librairies réunies ainsi :
» NumPy
» SciPy
» matplotlib
» Jupyter
» SymPy
» pandas

La librairie qui porte le nom SciPy propose des routines de traitement numériques, notamment
d’intégration et d’optimisation. C’est une librairie à usage général qui répond à des besoins
variés et assure le support des librairies plus spécifiques telles que Scikit-learn, Scikit-image et
statsmodels.

Calculs scientifiques fondamentaux avec NumPy


La librairie NumPy (http://www.numpy.org) permet de réaliser des manipulations de tableaux
à N dimensions, très utilisés en datalogie. Le jeu de données d’exemple Boston déjà rencontré
incarne un tableau à une dimension. Vous ne pourriez pas y accéder facilement sans disposer
des fonctions NumPy qui supportent l’algèbre linéaire, les transformées de Fourier et la
génération de valeurs aléatoires. La liste des fonctions qu’il contient est disponible à l’adresse
http://docs.scipy.org/doc/numpy/reference/routines.html.

Analyse de données avec pandas


La librairie nommée pandas (http://pandas.pydata.org) offre un soutien pour les structures
de données et les analyses de données. Elle est optimisée pour garantir de bonnes
performances en science des données. L’objectif de cette librairie est de fournir sous Python les
mêmes possibilités d’analyse et de modélisation de données qu’un langage dédié tel que R.

Apprentissage machine avec Scikit-learn


La librairie Scikit-learn (http://scikit-learn.org) est l’une des nombreuses librairies de
Scikit qui profite des fonctions offertes par les deux librairies NumPy et SciPy. Cette librairie
se concentre sur le minage et l’analyse de données. Voici le genre de domaines fonctionnels
auxquels elle donne accès :
» Classification
» Régression
» Regroupement, clustering
» Réduction dimensionnelle
» Sélection de modèle
» Prétraitement

Certains de ces noms correspondent à un titre d’un des chapitres suivants. Vous pouvez donc
en déduire dès maintenant que la librairie Scikit-learn est la plus importante du livre, même si
elle dépend pour fonctionner de librairies plus élémentaires.

L’apprentissage profond avec Keras et TensorFlow


Keras (https://keras.io) est le nom d’une interface de programmation API qui permet
d’effectuer des entraînements de modèles pour apprentissage approfondi. Rappelons qu’une
interface API propose un modèle pour réaliser un traitement, mais c’est à vous de fournir
l’implémentation. C’est ce rôle que joue l’outil TensorFlow (https://www.tensorflow.org).
Vous pouvez également opter pour l’outil de Microsoft CNTK
(https://www.microsoft.com/en-us/cognitive-toolkit/) ou encore Theano
(https://github.com/Theano). Dans ce livre, nous nous concentrons sur TensorFlow.
Adopter une API permet de se simplifier la vie. Voici pourquoi Keras va simplifier la vôtre :
» Une interface cohérente. L’interface de Keras est optimisée pour répondre aux cas
d’utilisation les plus fréquents ; elle privilégie l’interaction avec l’utilisateur, notamment
pour corriger les erreurs.
» Une approche modulaire. L’approche par blocs fermés permet de créer facilement vos
modèles en interconnectant les blocs de construction. Vous ne subissez que quelques
contraintes dans la façon dont vous les interconnectez.
» Extensible. Vous pouvez facilement ajouter puis personnaliser de nouveaux blocs pour
résoudre les besoins tels que de nouveaux niveaux, des fonctions de perte et des modèles.
» Traitements parallèles. Pour que vos applications offrent de bonnes performances, vous
devez disposer d’un support correct des traitements en parallèle. Keras exploite aussi bien
les processeurs centraux CPU que les processeurs graphiques GPU.
» Support direct de Python. Vous n’avez rien à prévoir pour que l’implémentation
TensorFlow de Keras fonctionne avec Python, alors que le travail de configuration peut être
non négligeable avec d’autres interfaces API.

Création de graphes avec matplotlib


La librairie matplotlib (http://matplotlib.org) propose une interface dans le style de l’outil
MATLAB pour produire des présentations graphiques à partir de vos analyses de données.
Actuellement, la librairie n’offre qu’une sortie en 2D, mais vous disposez d’un grand choix de
représentations graphiques des motifs de données. Sans l’aide d’une telle librairie, vous ne
pourriez pas facilement créer des représentations qui communiquent le sens de vos analyses
de données aux non-spécialistes.

Créations de graphiques avec NetworkX


Pour étudier efficacement les relations qui existent dans des données complexes en réseau,
comme celles recueillies par un système de géolocalisation GPS lors des déplacements, il faut
disposer d’une librairie qui permette de créer, de modifier et d’étudier la structure des
données de différentes façons. La librairie doit permettre de produire des résultats
compréhensibles par des humains, notamment au format graphique. C’est ce que permet la
librairie NetworkX (https://networkx.github.io). L’avantage de cette librairie est que les
nœuds que vous choisissez dans le réseau peuvent être incarnés par des images et les limites
peuvent correspondre à des données arbitraires, ce qui vous permet de réaliser des analyses
très variées, largement plus qu’avec un code spécifique (code qui serait de plus très long à
rédiger).

Analyse de documents HTML avec Beautiful Soup


La librairie Beautiful Soup permet d’analyser des fichiers de données HTML ou XML afin de les
rendre exploitables en Python. Elle permet notamment de travailler avec des données sous
forme arborescente.
Cette librairie simplifie l’exploitation de documents au format HTML. Elle convertit
automatiquement le codage de ces documents depuis le format UTF-8 vers Unicode.
Normalement, le programmeur Python a besoin de s’occuper lui-même de ce genre de
conversion. Avec Beautiful Soup, cette tâche vous est épargnée.
La librairie Beautiful Soup est disponible à l’adresse suivante :

https://pypi.python.org/pypi/beautifulsoup4/
Chapitre 3
Mise en place de l’atelier Python
DANS CE CHAPITRE :
» Obtention d’une solution préconfigurée

» Installation d’Anaconda sous Linux, MacOS et Windows

» Obtention et installation des jeux de données et exemples

our pouvoir pratiquer la datalogie avec Python, il faut d’abord mettre en place un atelier et
P récupérer les jeux de données et les exemples pour mettre en pratique les exercices du livre.
Voyons donc d’abord comment configurer votre machine.

Le livre se base sur la version 5.5.0 de Jupyter Notebook fournie avec Anaconda 5.2.0. La
version de Python concernée est la 3.6.5. C’est celle qui a servi à créer les exemples. Vous
devez donc utiliser une version de Python au moins égale à celle-ci. Pour mettre toutes les
chances de votre côté, utilisez la version qui a été intégrée à Anaconda 3 en version 5.2.0. Les
versions plus anciennes ne vont pas disposer de toutes les fonctions requises et les versions
plus récentes pourraient ne plus être compatibles avec nos exemples. Certains d’entre vous
préféreront un autre outil que Jupyter Notebook. Nous en citerons quelques-uns dans ce
chapitre. Bien sûr, si vous choisissez d’autres outils, les captures d’écran et les descriptions
des chapitres suivants ne correspondront pas. Cela dit, à partir du moment où l’outil choisi
supporte Python en version 3.6.5, les exemples devraient fonctionner de la même façon.
Le fait de télécharger le code source des exemples ne doit pas vous empêcher de vous
entraîner à écrire du code, de le faire analyser par un débogueur, de l’enrichir et de vous
resservir des exemples. Le code vous est fourni déjà saisi pour accélérer votre découverte de
Python et de la datalogie. Une fois que vous avez pris l’habitude de lire ce genre de code
source, vous ne devez pas hésiter à commencer vos propres projets. Vous pouvez même
ressaisir les exemples et comparer avec le code source récupéré afin de repérer vos erreurs
éventuelles. Le code source de la totalité de ce chapitre se trouve dans le fichier nommé
PYDASC_03.ipynb. Le jeu de données correspondant se trouve dans le fichier
PYDASC_03_Dataset_Load. (Nous avons indiqué dans l’introduction comment récupérer le
fichier compressé des exemples.)

Quelques paquetages scientifiques préconfigurés


Il est tout à fait possible d’installer uniquement le langage Python, puis d’y ajouter toutes les
librairies scientifiques nécessaires. Ce n’est pas un travail facile, car c’est à vous de vérifier
que les versions de toutes les librairies sont compatibles entre elles et avec Python. Vous
devrez ensuite effectuer la configuration afin que toutes les librairies soient accessibles. Vous
pouvez vous épargner cet important effort en optant pour un des paquetages de science de
données préconfigurés pour Python. Vous disposez ainsi en un seul geste de tout ce qu’il faut
pour vous lancer dans vos premiers projets de datalogie.
Vous pouvez choisir l’un des trois paquetages que nous présentons dans la suite de cette
section, mais tous les codes source des exemples du livre ont été construits avec Anaconda, car
ce paquetage est disponible sur les trois plates-formes que sont Linux, MacOS X et Windows.
Si vous choisissez un autre atelier, les captures ne correspondront pas et vous devrez sans
doute retoucher le code.

Anaconda de Continuum Analytics


Le paquetage principal Anaconda est disponible gratuitement à l’adresse suivante :

https://www.anaconda.com/download
Il est possible qu’il faille se rendre à la page https://repo.anaconda.com/archive pour
récupérer la version 5.2.0 qui n’est plus la plus récente à l’heure où vous lisez ceci. Choisissez
l’un des liens vers une version 3.6 de Python. Le fichier récupéré doit commencer par la
mention Anaconda3-5.2.0- suivie du nom de la plate-forme et d’une mention _32 bits ou _64
(bits). Le fichier s’appellera par exemple Anaconda3-5.2.0-Windows-x86_64.exe pour la
version Windows 64 bits. Voici les systèmes d’exploitation reconnus par Anaconda :
» Windows, en 32 bits et en 64 bits (il est possible que l’installateur ne propose qu’une des
deux versions s’il détecte celle applicable).
» Linux, en 32 bits et en 64 bits ;
» MacOS X, en 64 bits.

La version téléchargée par défaut contient Python en version 3.6, celle que nous avons utilisée
dans le livre. Si vous avez de bonnes raisons de diverger, vous pouvez aussi choisir d’installer
Python dans l’ancienne version 2.7 en utilisant le lien approprié. Aussi bien sous Windows que
sous MacOS X, vous disposez d’un installateur à interface graphique. Sous Linux, vous aurez
recours à l’utilitaire bash sur ligne de commande.
Nous déconseillons bien sûr d’installer Anaconda avec une ancienne version de Python, de la
lignée 2.x.

Vous n’avez pas besoin d’autres produits que ce paquetage gratuit dans ce livre. Lors de votre
navigation sur le site Anaconda, vous allez sans doute voir de nombreux autres produits
complémentaires, qui permettront de créer des applications fiabilisées. Vous pouvez par
exemple ajouter Accelerate pour mieux exploiter des machines multicœurs et activer les
traitements sur le processeur graphique GPU. Nous n’abordons pas ces extensions dans ce
livre, mais vous trouverez tous les conseils appropriés sur le site Anaconda.

Canopy Express de Entought


Le paquetage Canopy Express est un autre produit gratuit permettant de créer avec Python
des applications techniques et scientifiques. Il est disponible à l’adresse
https://www.enthought.com/product/canopy. Notez que seule la variante Express est
gratuite. Ce produit supporte les mêmes trois systèmes qu’Anaconda.
Si vous optez pour Canopy, vérifiez que vous téléchargez une version qui supporte Python en
version 3.6. Dans le cas contraire, tous les exemples ne fonctionneront pas. Rendez-vous par
exemple à la page https://www.enthought.com/product/canopy/#/package-index.

Lorsque vous lancez le téléchargement, vous verrez apparaître un formulaire qui vous invite à
fournir des informations à votre sujet. Sachez que le téléchargement fonctionne même si vous
ne saisissez pas vos données.
Un des points forts de Canopy Express est que la société Entought assure un excellent support
aussi bien pour les étudiants que pour les formateurs. Des formations en ligne sont disponibles
pour apprendre à utiliser l’outil (https://training.enthought.com/courses). Vous trouverez
également des formations spécifiques aux activités de science de données.

Le paquetage WinPython
Comme son nom l’indique, ce paquetage n’est disponible que sous Windows. Vous pouvez vous
le procurer à l’adresse http://winpython.github.io. Le produit supporte Python à partir de
la version 3.5. Il s’agit d’une reprise en main du produit Python(x,y), un atelier dont le
développement a été stoppé en 2015.
L’avantage de ce produit est sa grande souplesse, mais c’est au détriment de son ouverture en
termes de plates-formes. Les programmeurs qui ont besoin de jongler avec plusieurs versions
du même atelier trouveront un intérêt à WinPython. Si vous optez pour ce produit, soyez très
vigilant au niveau des configurations, car le code des exemples risque sinon de ne pas
fonctionner.

Installation d’Anaconda sous Windows


Dans sa version Windows, Anaconda dispose d’un installateur graphique basé sur un assistant.
Nous supposons dans la suite que vous avez déjà procédé au téléchargement du paquetage
Anaconda comme indiqué dans les pages précédentes. La procédure qui suit fonctionnera aussi
bien en 32 qu’en 64 bits.
1. Localisez le fichier téléchargé du paquetage Anaconda.
Rappelons que le nom du fichier suit le schéma Anaconda3-5.2.0-Windows-x86.exe dans
le cas d’un système 32 bits (en 64 bits, s’y ajoute avant le suffixe la mention _64). Le
numéro de version fait partie du nom de fichier. Dans notre exemple, c’est la version 5.2.0.
2. Double-cliquez le nom du fichier d’installation.
Si vous voyez apparaître une boîte d’avertissement de sécurité, cliquez le bouton Run ou
Exécuter. Vous voyez alors la première étape de l’installation (Figure 3.1). Le contenu exact
varie en fonction de la version que vous installez. Si votre système d’exploitation fonctionne
en 64 bits, utilisez de préférence cette version d’Anaconda pour obtenir de bonnes
performances.
3. Cliquez le bouton Suivant (Next).
Vous voyez apparaître un accord de licence. Parcourez-le si nécessaire.

Figure 3.1 : Première étape de l’installation d’Anaconda qui permet de savoir si vous êtes en 64 bits.

4. Cliquez le bouton d’acceptation pour montrer que vous êtes d’accord avec les
conditions de licence (I agree).
La Figure 3.2 montre qu’il vous est alors demandé quel type d’installation vous voulez
réaliser. En général, il suffit d’installer le produit seulement pour vous. Vous choisirez l’autre
option si d’autres comptes utilisateurs sur la même machine doivent pouvoir accéder à
Anaconda.

Figure 3.2 : Choix du mode d’installation d’Anaconda.

5. Choisissez parmi les deux options puis cliquez Suivant ou Next.


L’assistant vous demande où installer Anaconda (Figure 3.3). Nous supposons que vous
acceptez le répertoire proposé par défaut. Si vous changez cet emplacement, vous devrez
retoucher à certaines des procédures dans la suite du livre.
Figure 3.3 : Choix de l’emplacement d’installation.

6. Changez l’emplacement si nécessaire, puis cliquez Suivant ou Next.


La Figure 3.4 montre la page des options avancées. Le choix prédéfini convient et il n’y a
aucune raison d’en changer. Dans ce livre, nous supposons que vous avez accepté les
options telles quelles.
L’option qui permet d’ajouter le chemin d’accès à Anaconda à la variable PATH n’est pas
activée par défaut. Si vous l’activez, les fichiers Anaconda seront disponibles depuis tous
les emplacements, même sur la ligne de commande, mais si vous installez plusieurs
versions Anaconda, seule la première version installée sera visée. Dans ce contexte
particulier, mieux vaut ouvrir une fenêtre d’invite Anaconda pour accéder à la version avec
laquelle vous voulez travailler.
7. Ne modifiez pas les options et cliquez Installer.
Vous voyez apparaître une jauge de progression d’installation. Le processus peut prendre
quelques minutes. Une fois l’installation achevée, un bouton de passage à la suite vous est
proposé.

Figure 3.4 : L’étape des options d’installation avancée.

8. Cliquez Suivant ou Next.


L’assistant vous confirme que l’installation s’est bien terminée.
9. Cliquez Suivant ou Next.
Anaconda vous propose alors d’ajouter un support pour le code Visual Studio. Nous n’en
avons pas besoin dans ce livre, et le fait d’ajouter cette option risque de modifier le
fonctionnement des outils Anaconda. À moins d’avoir vraiment besoin de ce support, ne
modifiez pas l’environnement Anaconda.
10.Cliquez Ignorer ou Skip.
La page qui apparaît offre les options pour en apprendre plus au sujet d’Anaconda Cloud et
propose des informations pour vous lancer dans votre premier projet. Vous activez ou non
ces options en fonction de ce que vous voulez faire maintenant. Cela n’a aucun effet sur
votre installation.
11.Sélectionnez les options désirées et cliquez Terminer ou Finish.
Vous voici maintenant prêt à commencer à profiter d’Anaconda.

À PROPOS DES CAPTURES D’ÉCRAN

Vous pouvez opter pour un autre environnement de développement pour profiter des fichiers de
Jupyter Notebook. Dans le livre, toutes les captures qui montrent une fenêtre d’édition ont été faites
avec Anaconda. Le fait que nous ayons choisi Anaconda ne signifie pas qu’il s’agisse du meilleur
environnement de développement, ni que les auteurs de ce livre le privilégient. Il se trouve
qu’Anaconda fonctionne bien pour ce genre de tutoriel.

Le nom de l’environnement graphique, Jupyter Notebook, est le même quelle que soit la plate-
forme avec laquelle vous travaillez. S’il y a des différences, elles sont négligeables et vous
pourrez les ignorer. Cela dit, les captures d’écran ont été pour la plupart réalisées sous
Windows. De légères différences sont à prévoir si vous travaillez sous Linux ou sous MacOS X.

Installation d’Anaconda sous Linux


Sous Linux, vous installez Anaconda depuis la ligne de commande car il n’y a pas d’installateur
graphique. Commencez par télécharger le paquetage depuis le site de Continuum Analytics
comme indiqué en début de chapitre. La procédure qui suit fonctionne aussi bien
en 32 qu’en 64 bits.
1. Ouvrez une fenêtre de terminal.
2. Naviguez parmi les répertoires jusqu’à arriver dans celui dans lequel se trouve
le fichier Anaconda que vous avez téléchargé.
Rappelons que le nom du fichier suit le schéma Anaconda3-5.2.0-Linux-x86.sh dans le
cas d’un système 32 bits (en 64 bits, s’y ajoute avant le suffixe la mention _64). Le numéro
de version fait partie du nom de fichier. Dans notre exemple, c’est la version 5.2.0.
3. Saisissez la commande bash suivie du nom du fichier.
L’assistant d’installation demande d’accepter la licence d’utilisation.
4. Lisez la licence, puis acceptez-la.
L’assistant vous demande de choisir où installer le produit. Nous supposons que vous
laissez l’emplacement proposé qui est ⁓/anaconda. Si vous changez cela, vous devrez
retoucher quelques procédures dans la suite du livre.
5. Fournissez si nécessaire un nouvel emplacement d’installation puis validez par
la touche Entrée.
Le processus d’extraction de l’archive commence. Une fois celui-ci terminé, un message
vous le confirme.
6. Ajoutez le chemin d’installation à votre variable PATH en utilisant la méthode
appropriée à votre version de Linux.
Vous êtes maintenant prêts à utiliser Anaconda.

Installation d’Anaconda sous MacOS X


L’installation sous MacOS X ne se fait qu’au format 64 bits. Après avoir téléchargé un
exemplaire depuis le site de Continuum Analytics (voir plus haut), procédez comme indiqué ci-
dessous.
1. Avec le Finder, localisez le fichier téléchargé.
Le nom du fichier suit le même modèle que sous Windows ou Linux, sauf que le nom du
système devient MacOS X.
2. Double-cliquez l’icône du fichier d’installation.
Une boîte de bienvenue apparaît.
3. Cliquez Continuer.
L’assistant vous demande si vous avez envie de lire les informations. Vous pouvez ignorer
cette proposition, car vous pourrez toujours lire cela plus tard.
4. Cliquez Continuer.
La licence vous est proposée. Lisez-la bien.
5. Cliquez pour confirmer que vous acceptez la licence.
L’assistant demande de fournir une destination. Le choix de la destination détermine si
l’installation ne sert qu’à un compte utilisateur ou à tout un groupe.
Si vous voyez apparaître un message d’erreur comme quoi Anaconda ne peut pas être
installé, c’est à cause d’un bogue dans l’installateur et non dans votre système. Pour éviter
ce message, choisissez l’option de l’installer que pour vous. Vous ne pouvez peut-être pas
installer Anaconda pour un groupe d’utilisateurs sous MacOS.
6. Cliquez Continuer.
L’installateur propose plusieurs options pour le type d’installation. Si vous voulez changer le
lieu d’installation, choisissez Change install location. Dans ce livre, nous supposons
que vous acceptez l’emplacement par défaut, ⁓/anaconda. Vous pouvez utiliser le bouton
Customize pour modifier le fonctionnement de l’installateur, par exemple en évitant
d’ajouter Anaconda à votre variable PATH. Dans ce livre, nous supposons que vous n’avez
rien changé aux options d’installation par défaut. Il n’y a aucune raison d’en changer, à
moins qu’une version de Python 2.7 soit installée sur le système.
7. Cliquez Installer.
Une jauge de progression montre l’avancement de l’installation, puis un message vous
confirme que le processus est terminé.
8. Cliquez Continuer.
Vous êtes maintenant prêt à utiliser Anaconda.

Récupération des données et des exemples


En récupérant le code source soigneusement mis au point, vous vous évitez de tâtonner pour
obtenir vos premiers succès en datalogie. De plus, des jeux de données vous sont proposés, car
il faut toujours un minimum de temps pour préparer des jeux sur lesquels faire des essais.
Grâce aux fonctions disponibles dans certaines librairies, vous allez pouvoir accéder à des jeux
de données prédéfinis et standardisés. Découvrons les grands principes d’utilisation des jeux
de données et des exemples qui vont vous faire gagner du temps pour vous mettre le plus vite
possible le pied à l’étrier.

L’outil Jupyter Notebook


Pour rendre plus simple l’utilisation du code assez complexe de ce livre, nous allons nous servir
de l’outil nommé Jupyter Notebook qui constitue une interface vers les fichiers au format
Notebook Python. Un seul fichier de calepin (notebook) peut contenir un certain nombre
d’exemples, et chaque exemple peut être exécuté indépendamment. Le programme s’utilise
dans votre navigateur. C’est la raison pour laquelle il peut fonctionner dans différents
environnements, à partir du moment où un navigateur Web en mode graphique est disponible.

Démarrage de Jupyter Notebook


En général, l’installateur a pris soin de créer une icône pour cet outil sur le bureau. S’il est
disponible, par exemple sous Windows, cliquez directement Jupyter Notebook. Sinon, ouvrez le
menu Démarrer, choisissez Tous les programmes, puis Anaconda 3 et enfin Jupyter
Notebook. L’aspect général de l’interface de l’outil est présenté dans la Figure 3.5, ici dans le
navigateur Firefox. Les détails visuels vont varier en fonction du navigateur et de votre
système d’exploitation.
Figure 3.5 : Vue générale de l’outil Jupyter Notebook.

Si vous ne voyez pas d’icône pour lancer l’outil, vous pouvez démarrer Jupyter Notebook ainsi :
1. Ouvrez une fenêtre de commande Anaconda Prompt, ou bien une fenêtre de
terminal.
Observez l’apparition de la fenêtre en mode texte, dans laquelle vous allez pouvoir saisir
une commande.
2. Naviguez dans les répertoires jusqu’à atteindre celui nommé \Anaconda3\Scripts
(utilisez des barres obliques à droite sous Linux et MacOS).
En général, vous disposez de la commande cd pour changer de répertoire.
3. Saisissez la commande suivante :

..\python Jupyter-script.py notebook

Vous devez voir apparaître une page dans votre navigateur avec Jupyter Notebook.

Arrêt du serveur Jupyter Notebook


Quel que soit le mode de démarrage de Jupyter Notebook, vous aurez remarqué que le système
ouvre d’abord une fenêtre en mode texte ou un terminal pour la partie serveur de l’outil.
Lorsque vous refermez la page du navigateur Web en fin de session, rendez active la fenêtre
dans laquelle s’exécute le serveur et arrêtez-le par la combinaison de touches Ctrl+C ou
Ctrl+Break.

Définition du référentiel de code


Tout le code que vous allez utiliser et créer dans ce livre sera stocké dans un référentiel sur le
disque dur. Un référentiel est une sorte d’armoire dans laquelle vous réunissez des fichiers de
code. L’outil Notebook ouvre un tiroir, récupère un dossier puis affiche le code qu’il y a trouvé.
Vous pouvez ensuite modifier ce code, exécuter chacun des exemples que contient le dossier,
en ajouter d’autres et interagir avec le code. Voyons les grands principes d’utilisation de
Notebook en relation avec ce concept de référentiel.

Définition d’un nouveau dossier


Pour chaque projet, vous allez définir au moins un dossier pour y stocker les fichiers de code.
Dans ce livre, nous proposons d’utiliser le radical PYDASC (abréviation de PYthon DAta
SCience). Voici comment créer un nouveau dossier :
1. Du côté droit de la fenêtre en haut, ouvrez le menu New et choisissez Folder
(Nouveau/Dossier).
Un dossier est créé avec un nom temporaire, sous Windows c’est Untitled Folder
(dossier sans titre). Vous aurez peut-être besoin de faire défiler la liste des dossiers
existants pour le retrouver.
2. Cochez la case de sélection de ce nouveau dossier, en marge gauche.
3. Dans la page, cliquez la commande Rename (renommer).
Vous voyez apparaître une boîte pour changer le nom du répertoire (Figure 3.6).

Figure 3.6 : Création d’un dossier pour tout le code des exemples du livre.

4. Saisissez le nom PYDASC puis validez par Entrée.


Le dossier porte le nom personnalisé.

Principe du calepin
Un calepin équivaut à un dossier dans lequel vous pouvez réunir plusieurs fichiers d’exemple.
À chaque exemple correspond une cellule. Le dossier peut contenir d’autres types d’éléments
(images, textes explicatifs), comme nous le verrons au fur et à mesure de la lecture du livre.

Création d’un nouveau calepin


Voici comment créer un nouveau calepin.
1. Dans la liste de votre page principale, cliquez le nom du dossier PYDASC.
Normalement, le dossier est vide, puisque vous venez de le créer.
2. Ouvrez le menu New à droite et choisissez Python 3.
Un nouvel onglet apparaît dans le navigateur montrant le nouveau calepin (Figure 3.7).
Vous remarquez qu’une cellule vide est proposée et sélectionnée, ce qui signifie que vous
pouvez saisir des caractères, et donc du code. Pour l’instant, le calepin n’a pas de titre.
Nous allons donner un titre plus parlant.

Figure 3.7 : Aspect d’un calepin de Notebook avec une seule cellule.

3. Cliquez la mention Untitled de la page.


L’outil Notebook vous demande si vous voulez vraiment changer le nom (Figure 3.8).
Figure 3.8 : Personnalisation du nom d’un calepin.

4. Donnez à ce calepin le nom suivant sans oublier de valider par la touche Entrée :

PYDASC_03_Test

Le nom proposé vous permet immédiatement de savoir qu’il s’agit du calepin des exemples du
Chapitre 3 de ce livre, avec une mention comme quoi il ne s’agira ici que d’un simple test.
Notez qu’un fichier de calepin va toujours porter l’extension .ipynb mais que nous n’avez pas
besoin de la saisir.

Ajout de contenu à un calepin


Un calepin permet de saisir du code Python, mais également du texte libre avec quelques
éléments de mise en forme. Essayons cela immédiatement :
1. La liste déroulante sous le menu Help (Aide) détermine le mode de saisie. Si elle
indique Code, ouvrez-la pour sélectionner Markdown.
En demandant à la cellule d’être de ce genre, vous informez l’outil que vous allez écrire du
texte de documentation et non du code Python. L’outil ne va jamais tenter d’interpréter le
contenu de ce genre de cellules, même si c’est du code Python. Ces cellules de
documentation permettent d’expliquer ce que vous voulez faire comprendre au sujet du
code source.
2. Saisissez le titre suivant, puis cliquez la commande Run ou Exécuter (le bouton
avec la flèche vers la droite butant sur un trait dans la barre d’outils) :

# Récupération des données et des exemples

N. d. T : Selon le système et la version de Notebook, vous verrez les menus en anglais ou


en français. C’est pourquoi nous indiquons les noms des commandes dans les deux
langues.

En commençant la saisie par un seul signe #, vous créez un titre de premier niveau ; avec
deux signes #, ce sera un second niveau, etc. Le texte qui suit le signe correspond au
contenu du titre. En utilisant la commande Run, vous faites comprendre ce que vous avez
saisi comme un titre (Figure 3.9). Vous remarquez que l’outil insère automatiquement une
nouvelle cellule pour poursuivre votre travail.
Figure 3.9 : Insertion d’un titre pour documenter le code.

3. Si nécessaire, choisissez à nouveau Markdown dans la liste Code puis saisissez le


sous-titre suivant avant de cliquer la commande Run :

## Définition du référentiel de code

Vous constatez que ce codage a été interprété comme sous-titre, puisque l’affichage utilise
une police légèrement plus petite que celle du titre principal.
4. Procédez de même pour saisir le sous-type de niveau 3 suivant :

### Ajout de contenu à un calepin

Vous avez maintenant inséré une hiérarchie à trois niveaux. Cette structuration permettra
de retrouver facilement les différents blocs de code. Vous remarquez que l’outil crée à
chaque fois une nouvelle cellule, en choisissant d’office le genre de cellule Code, qui est
celui permettant de saisir du code source. Après tout, c’est votre activité principale avec cet
outil.
5. Saisissez donc l’instruction Python suivante puis cliquez la commande Run :

print("Python est vraiment sympa !")

Vous remarquez que le code est en couleur, ce qui confirme que l’outil a reconnu la fonction
print() et l’a distinguée des données que vous lui fournissez (Python est vraiment
sympa). Le résultat est visible dans la Figure 3.10. Notez que le résultat affiché fait partie
de la même cellule que le code qui en est à l’origine. Notebook sépare visuellement le
résultat du code pour plus de confort. Bien sûr, une nouvelle cellule est ajoutée d’office à la
suite.

Figure 3.10 : Votre première ligne IPython exécutée.

Il faut penser à refermer correctement votre outil dès que vous avez terminé de travailler dans
un calepin. Pour quitter un calepin, choisissez la commande File/Close and Halt
(Fichier/Fermer et stopper). Vous revenez à la page présentant la liste du contenu du
dossier PYDASC. Elle ne devrait montrer que le calepin que vous venez de créer (Figure 3.11).
Pour sauvegarder l’état actuel du calepin, utilisez la commande File/Save and checkpoint
(Fichier/Enregistrer et checkpoint).

Figure 3.11 : Les calepins créés apparaissent dans la liste du référentiel.

Exportation d’un calepin


Vous aurez rapidement envie de partager certains de vos calepins avec d’autres personnes. Il
suffit pour ce faire d’exporter un calepin depuis le référentiel vers un fichier indépendant. Vous
pouvez ensuite transmettre ce fichier à votre destinataire, qui n’aura plus qu’à l’importer dans
son propre référentiel.
Dans la section précédente, nous avons créé le calepin portant le nom PYDASC_03_ Test. Si
vous l’avez refermé, ouvrez-le à nouveau depuis la liste du référentiel (un simple clic suffit).
Pour exporter le code, choisissez la commande File/ Download as (Fichier/Télécharger)
puis Notebook (ipynb). La suite varie selon le navigateur utilisé. Vous devriez voir apparaître
une boîte pour enregistrer le fichier du calepin. Vous utilisez la même technique que celle
permettant d’enregistrer n’importe quel autre fichier depuis votre navigateur.

Suppression d’un calepin


Vous aurez parfois besoin de faire le ménage dans votre référentiel et de supprimer les
calepins qui ne servent plus. Pour aérer votre référentiel, il suffit de demander de supprimer
certains calepins. Servez-vous à cet effet de la case à cocher de marge gauche qui sert à
sélectionner chaque ligne dans la liste. Revoyez dans la Figure 3.11 la marge gauche du
calepin PYDASC_03_Test. Voici comment supprimer ce calepin :
1. Cochez la case à gauche de l’entrée correspondant au calepin désiré.
2. Dans la série de boutons qui apparaît, cliquez l’icône de corbeille Delete.
Un message vous demande de confirmer que vous voulez effectivement supprimer ce
calepin (Figure 3.12).
3. Cliquez la demande de confirmation. Le calepin disparaît la liste.

Figure 3.12 : Demande de confirmation avant suppression d’un calepin dans le référentiel.

Importation d’un calepin


Pour pouvoir profiter des exemples du livre, il faut savoir importer les fichiers des différents
chapitres dans votre référentiel. Vous avez certainement déjà téléchargé le fichier archive du
livre. Il faut ensuite l’extraire dans un dossier de votre convenance sur le disque dur. L’archive
contient toute une série de fichiers de calepin IPython, à extension .ipynb. Si nécessaire,
revoyez l’introduction qui explique comment récupérer le fichier des exemples.
Voici comment importer ces fichiers dans le référentiel :
1. Dans la page du référentiel, du côté droit à côté du bouton New, cliquez la
commande Upload.
Les modalités exactes d’ouverture d’un fichier depuis votre navigateur varient selon le
modèle choisi.
2. Naviguez jusqu’au répertoire contenant les fichiers à importer.
3. Sélectionnez-le ou les fichiers à importer puis cliquez le bouton permettant de
commencer le processus de téléchargement (Open ou Ouvrir, en général).
Vous voyez que le fichier apparaît dans une liste des téléchargements sélectionnés
(Figure 3.13). Pour l’instant, le fichier n’est pas encore inséré dans le référentiel. Il n’est que
candidat.

Figure 3.13 : Sélection des fichiers à télécharger pour les ajouter au référentiel.

4. Cliquez le bouton Upload pour lancer le téléchargement.


L’outil va ajouter le fichier dans votre référentiel, ce qui le rend ensuite accessible.

Tour d’horizon des jeux de données du livre


Plusieurs jeux de données ont été préparés pour ce livre. Tous ont été intégrés à la librairie
nommée scikit-learn. Ces jeux permettront de voir comment interagir avec les données et
appliquer différents traitements. Voici une sélection de fonctions permettant d’importer des
jeux de données dans votre code source Python :
» load_boston() : analyse de régression avec les prix immobiliers de Boston.
» load_iris() : classification avec le jeu de données Iris.
» load_diabetes() : régression avec un jeu de données médicales concernant le diabète.
» load_digits([n_class]) : classe de classification avec le jeu de données digits.
» fetch_20newsgroups(subset=‘train’) : données de 20 groupes de news.
» fetch_olivetti_faces() : jeu de données des visages Olivetti de ATT.

La technique de chargement est la même pour tous les jeux de données. Voici par exemple
comment charger les données immobilières de Boston. Le code correspondant se trouve dans
le calepin nommé PYDASC_03_Dataset_Load.

from sklearn.datasets import load_boston


Boston = load_boston()
print(Boston.data.shape)

Vous pouvez voir immédiatement fonctionner le code en utilisant la commande Run Cell. La
fonction d’affichage print() indique 506, 13, comme le montre la Figure 3.14. Patientez
quelques secondes que le jeu de données entier soit chargé.
Figure 3.14 : Chargement des données de l’objet Boston.
Chapitre 4
Google Colab
DANS CE CHAPITRE :
» Présentation de Google Colab

» Création de votre compte Colab

» Les tâches de base dans Colab

» Partage d’un calepin

L ’outil nommé Colab de


(https://colab.research.google.com/notebooks/welcome.ipynb) est un
Google
service
disponible en ligne qui reprend le principe de l’outil Jupyter Notebook, mais dans l’infonuage
(cloud). Vous n’avez donc rien à installer sur votre système. L’utilisation de Colab ressemble à
celle de Jupyter Notebook. Ce chapitre est destiné à ceux qui ont soit décidé de ne pas installer
Notebook, soit doivent utiliser un autre terminal qu’un ordinateur habituel, par exemple une
tablette, et veulent pratiquer les exemples.
La version de Colab qui sera disponible en ligne lorsque vous allez vouloir vous en servir va
peut-être rendre certains des exemples du livre non fonctionnels, ou bien les résultats se
présenteront de façon différente. La prochaine section donne quelques détails concernant le
principe qui régit Colab. L’essentiel est de ne pas oublier que Colab n’est pas un remplacement
exact de l’outil Jupyter Notebook. Nous n’avons pas testé les exemples de façon exhaustive
pour cet environnement en ligne. Cela dit, la plupart devraient fonctionner, aussi n’hésitez pas
à les essayer si vous deviez utiliser cette variante de Notebook.
Pour utiliser Colab, vous devez disposer d’un compte Google. La plupart des possibilités de
Colab sont inaccessibles si vous ne vous êtes pas authentifié. Voyons donc comment faire vos
premiers pas avec Colab.
Comme l’outil Notebook, Colab exploite le principe des cellules successives. Nous allons
réaliser plusieurs activités dans la suite du chapitre pour le vérifier. Si vous avez lu les
chapitres précédents, vous constaterez une forte ressemblance entre l’outil Notebook et
Google Colab.
Ce chapitre ne présente pas toutes les caractéristiques de Colab. La dernière section donne
des pistes pour en apprendre plus au sujet de cet outil.

Présentation de Google Colab


Colab est une sorte de version à distance de l’outil Notebook. D’ailleurs, la page de bienvenue
le rappelle. Les fichiers utilisés sont les mêmes que ceux de l’outil IPython, c’est-à-dire des
fichiers de calepins au format .ipynb. Autrement dit, vous pouvez voir directement vos
calepins dans le navigateur Web. Malgré ces points communs, les deux applications se
distinguent sous plusieurs aspects, ce que nous allons présenter dans la suite de cette section.

Ce que permet Google Colab


Colab permet de rédiger du code source et de le faire exécuter, de produire la documentation
correspondante et même d’afficher les représentations graphiques, comme avec Jupyter
Notebook. Les techniques utilisées sont les mêmes, si l’on excepte quelques petites différences
dont nous parlerons en fin de chapitre. Le code source des exemples du livre devrait
fonctionner sans souci.
L’outil Notebook installé dans le chapitre précédent est une application locale, qui fonctionne
donc d’abord avec des fichiers locaux. Il est bien sûr possible d’utiliser des fichiers distants,
mais les fichiers qui sont implantés dans un référentiel distant tel que GitHub sont proposés
sous forme de pages statiques au format HTML. Google Colab est au contraire bien adapté à
l’utilisation de fichiers de calepin Notebook depuis un référentiel GitHub. D’ailleurs, Colab
propose plusieurs options de stockage en ligne, ce qui en fait un bon outil pour créer du code
Python en toute mobilité.
La principale raison d’utiliser Colab à la place de Jupyter Notebook est qu’il permet de
travailler avec un autre périphérique qu’un PC, notamment une tablette. Nous avons testé
plusieurs des exemples depuis une tablette Android (Asus ZenPad 3S 10). La tablette était
dotée du navigateur Chrome : le code s’est exécuté de façon satisfaisante. Pour autant, vous
devrez vous armer de courage si vous voulez rédiger du code source sur une tablette aussi
petite. Le texte est difficile à lire et l’absence d’un clavier est rédhibitoire. Le vrai avantage,
c’est que vous pouvez ainsi pratiquer de la science des données même sans disposer d’un
système Windows, Linux ou MacOS X. Le niveau de performances ne sera pas le même bien
sûr.

COLAB AVEC FIREFOX

Si votre navigateur Web favori est Firefox, il est possible que vous voyiez apparaître un message
d’erreur lors de l’accès à Colab. Le contenu initial du message suggère de modifier les cookies, et en
accédant aux détails, vous apprenez qu’il s’agit d’une opération non sûre. Si vous acceptez le
message par OK, vous aurez l’impression que Colab fonctionne correctement, mais l’exécution du
code ne sera pas possible.
Vérifiez d’abord que votre Firefox est récent. Vous pouvez ensuite résoudre le problème en donnant la
valeur True au paramètre network.websocket. allowInsecureFromHTTPS dans About : Config,
mais cela ne suffit pas toujours. Vérifiez alors que votre Firefox autorise les cookies tiers en activant
tous les cookies tiers ainsi que, si elle est proposée, l’option Données des sites. De plus,
choisissez de conserver les historiques dans la section Historique de la page Vie privée et
sécurité des options. Redémarrez Firefox après chaque modification, puis tentez d’accéder à Colab.
Si rien n’y fait, vous devez utiliser le navigateur Chrome pour travailler avec Colab.

Google Colab ne prétend pas fonctionner avec d’autres navigateurs que Chrome et Firefox.
Vous risquez de rencontrer des erreurs et une absence d’affichage si le navigateur avec lequel
vous voulez accéder à Colab ne convient pas. Si vous utilisez Firefox, revoyez le texte encadré
qui montre comment faire fonctionner Colab. Voyez aussi la section sur le support local
d’exécution un peu plus loin. Les efforts de configuration que vous aurez à déployer dépendent
du niveau de fonctions dont vous avez besoin. La plupart des exemples fonctionnent
correctement dans Firefox sans aucune modification.

Codage local ou télécodage


L’utilisation générale de Colab ressemble à celle de l’outil Notebook. En revanche, l’exécution
du code d’une cellule est différente : vous cliquez dans la cellule puis vous cliquez le bouton
d’exécution de la cellule. La cellule reste sélectionnée, ce qui oblige à sélectionner la
prochaine cellule pour l’exécuter à son tour. Le pavé dans la ligne des résultats permet de les
effacer, sans toucher aux autres cellules. Lorsque vous amenez le pointeur de souris sur le bloc
sans cliquer, vous apprenez qui a exécuté ce contenu. Du côté droit de la cellule, des points
verticaux désignent un menu d’options de cellule. Les résultats sont donc les mêmes qu’avec
Notebook, mais la façon d’y parvenir est différente.
La gestion du code source est différente de celle de Notebook. La rédaction du code et
l’exécution sont les mêmes que dans Notebook, mais c’est la façon dont vous gérez le code
source et le stockez qui change. Vous pouvez importer du code depuis un disque local puis
l’enregistrer sur un télédisque Google Drive ou un référentiel GitHub. Le code devient ensuite
accessible depuis n’importe quel appareil en accédant à cette adresse. Il suffit d’utiliser Colab
pour y accéder.
Si vous utilisez Colab avec le navigateur Chrome, vous pouvez choisir de synchroniser votre
Chrome entre plusieurs appareils, ce qui rend le code source accessible depuis chacun d’eux.
Vos choix sont propagés à tous vos appareils s’ils sont paramétrés pour synchroniser leur
configuration. Cela vous permet de commencer à écrire du code à votre bureau, de le tester
depuis une tablette dans le train puis de le retoucher depuis votre téléphone dans le jardin. Le
code est le même, le référentiel est unique et la configuration du navigateur Chrome est la
même, d’un appareil à l’autre.
Cette souplesse se paye en performances et en cohérence de l’interface. Vous devinez qu’une
copie locale de l’outil Notebook va exécuter le code bien plus vite qu’une copie de Colab quelle
que soit sa configuration, même donc avec une copie locale du fichier du calepin. Vous gagnez
de la souplesse au détriment de la vitesse avec Colab. Par ailleurs, la lecture du code source
n’est pas très confortable sur une tablette, et sur un telliphone encore moins. Si vous
agrandissez les caractères, vous ne verrez pas assez de contexte pour comprendre de quoi il en
retourne, et encore moins pour aller le modifier. Vous pouvez éventuellement relire le code une
ligne à la fois pour essayer de comprendre son fonctionnement.
L’outil Jupyter Notebook a d’autres avantages. Avec Colab, vous ne pouvez récupérer les
fichiers source que dans l’un des deux formats .ipynb ou .py. Les autres options sont
inaccessibles à Colab, et notamment le HTML, le LaTex et le PDF. Vous êtes donc limité au
niveau des présentations complètes que vous pouvez produire d’un calepin. Colab et Notebook
offrent deux expériences de codage de programmation différentes. Mais rien ne vous empêche
d’utiliser les deux tour à tour, puisque le format de fichier est commun. En théorie, vous
devriez pouvoir passer d’un outil à l’autre dans un même projet.
La terminologie utilisée dans les deux produits Notebook et Colab est similaire, et les fonctions
sont pour la plupart les mêmes, mais ils ne sont pas totalement identiques pour autant. La
façon de réaliser des tâches change : par exemple, une cellule de genre Markdown dans l’outil
Notebook correspond à une cellule texte dans Colab. La section ultérieure sur l’utilisation de
base de l’outil décrit quelques autres différences.
N. d. T. : Une différence visible pour les francophones est l’interface localisée en français pour
l’outil Google Colab, alors que Notebook n’est par défaut pas proposé dans une autre langue
que l’anglais (mais cela varie aussi selon le système d’exploitation).

Support d’exécution locale


Vous aurez absolument besoin d’un outil installé localement et permettant une exécution locale
pour travailler en équipe, et dès qu’il vous faut disposer des meilleures performances
possibles. Une installation locale permet facilement d’accéder à tous les fichiers stockés
localement et bien sûr de choisir quelle version de l’outil Notebook installer pour exécuter le
code source. Ces considérations de support d’exécution locale sont décrites dans la page
https://research.google.com/colaboratory/local-runtimes.html.
Pour profiter d’un support d’exécution locale, il faut bien sûr que la machine concernée
fonctionne sous Windows, Linux ou MacOS X. Certaines tablettes répondent à cette contrainte.
Vous devez également pouvoir y utiliser un des deux navigateurs acceptés. Il est quasiment
certain qu’Internet Explorer ou ses successeurs vont entraîner des soucis, ou même ne pas du
tout fonctionner avec Notebook.
Une considération essentielle lorsque vous décidez de travailler localement est que les fichiers
de code source Notebook deviennent les cibles éventuelles d’une infection. Vous devez pouvoir
faire confiance aux partenaires qui vous fournissent du code source. En revanche, cela ne rend
pas votre machine accessible à ceux avec lesquels vous partagez du code source. Chacun doit
utiliser son exemplaire installé localement de l’outil, ou bien travailler dans l’infonuage.
Si vous utilisez Colab avec un support d’exécution locale dans Firefox, vous devez prévoir
quelques opérations de configuration. Cherchez sur le Web les descriptions de la configuration
de Firefox pour Colab, et revoyez le texte encadré concernant l’exécution locale en début de
cette section. Vous devez toujours bien vérifier la configuration, car Firefox peut sembler
fonctionner comme il faut avec Colab, jusqu’au moment où un problème empêche de réaliser
une tâche en particulier. Dans ce cas, Colab va afficher des messages informant que le code n’a
pas pu être exécuté, ou une autre cause probable qui ne vous aidera pas vraiment à résoudre
le problème.

Accès à un compte Google


Pour pouvoir faire quelque chose d’intéressant dans Colab, vous devez vous authentifier avec
un compte Google. Vous pouvez vous servir du compte dont vous disposez déjà, par exemple
pour votre messagerie Gmail. Certains se servent d’un compte Google pour accéder à Google
Docs (https://www.google.com/docs) qui permet de gérer ses documents en ligne, un peu
comme la suite Microsoft Office 365.

Création d’un compte


Vous créez facilement un compte Google en visitant la page d’accueil
https://account.google.com puis en cliquant Créer votre compte. La page donne des
informations au sujet de ce qu’un compte Google peut vous apporter. Dès que vous cliquez le
lien de création, vous voyez la page montrée en Figure 4.1. Prévoyez de passer par plusieurs
pages et saisissez les informations demandées en cliquant Suivant.
Figure 4.1 : L’assistant de création de compte Google.

Ouvrir son compte


Une fois que vous avez créé votre compte, vous pouvez vous authentifier pour y entrer. C’est
indispensable pour bien utiliser Colab qui a besoin pour certaines fonctions de vous autoriser à
accéder à votre disque à distance Google Drive. Bien sûr, vous pouvez stocker vos calepins
ailleurs, par exemple dans le référentiel GitHub, mais grâce à votre compte Google, tout
pourra fonctionner comme prévu. Accédez donc à la page d’accueil, saisissez votre adresse de
messagerie puis votre mot de passe et cliquez Suivant pour voir apparaître la page de la
Figure 4.2.

Figure 4.2 : Page d’accès aux fonctions principales et à votre disque réseau.

Utiliser des calepins (notebooks)


Comme avec l’application Jupyter Notebook, les interactions avec Colab sont fondées sur le
concept de calepin, notebook. Vous pouvez voir différentes interactions possibles dans la page
d’accueil de Colab (https://colab.research.google.com/notebooks/welcome.ipynb) en
amenant le pointeur de la souris sans cliquer. Vous pouvez ainsi ajouter du code source ou du
texte de commentaires. Toutes les entrées sont actives. Vous pouvez déplacer des cellules et
copier le travail réalisé vers votre disque Google Drive. La page d’accueil est intéressante à
découvrir, mais l’objectif du chapitre est de montrer comment interagir avec un calepin.
Voyons comment réaliser les tâches les plus importantes dans Colab.

Création d’un calepin


Pour démarrer un nouveau calepin, ouvrez le menu Fichier (File) et choisissez Nouveau
notebook en Python 3 (Figure 4.3). Le résultat ressemble beaucoup à celui de l’outil
Notebook, avec quelques divergences qui n’enlèvent rien à la richesse fonctionnelle. Vous avez
remarqué que vous pouvez également travailler en Python 2, mais ce n’est pas le sujet de ce
livre.
Figure 4.3 : Ouverture d’un nouveau calepin Python 3 dans Colab.

Cliquez dans le nom temporaire attribué au fichier pour le rendre plus suggestif comme nous
l’avons fait avec Notebook. Pour faire exécuter le code d’une cellule, vous cliquez dans l’icône
de flèche pointant vers la droite sur le bord gauche de la cellule. L’exécution ne provoque pas
nécessairement la sélection de la cellule suivante. Dans ce cas, cliquez pour la sélectionner ou
bien utilisez les boutons de navigation de cellule en cellule de la barre d’outils.

Chargement d’un calepin existant


Vous pouvez charger un calepin qui se trouve sur votre machine, sur Google Drive ou dans le
référentiel GitHub. Vous pouvez bien sûr aussi télécharger un calepin par exemple depuis un
disque réseau. Dans tous les cas, la commande correspondante est Fichier/Ouvrir un
Notebook qui donne accès à la page de la Figure 4.4.
La liste qui apparaît montre tous les fichiers récemment ouverts, tous emplacements
confondus, dans l’ordre alphabétique. Vous disposez d’une fonction de filtrage, parmi d’autres
options dans le haut de la liste.
Les projets d’exemple Colab sont accessibles même si vous n’avez pas ouvert votre session. Ils
vous permettent de comprendre les grands principes de Colab, mais pas de créer vos projets.

Figure 4.4 : Boîte de dialogue d’ouverture d’un calepin existant.

Chargement d’un calepin depuis Google Drive


Pour la plupart des opérations dans Colab, l’emplacement choisi par défaut est le disque
Google Drive. Vous pouvez toujours le désigner comme lieu de stockage. Son contenu est
présenté sous forme d’une liste comme celle de la Figure 4.4 ci-dessus. Pour charger un fichier,
vous cliquez directement le lien dans la boîte, le fichier s’ouvrant dans la page actuelle du
navigateur.
Chargement d’un fichier depuis GitHub
Pour travailler avec un référentiel GitHub, vous devez d’abord fournir le chemin d’accès à
votre référentiel en ligne (Figure 4.5).

Figure 4.5 : Boîte d’ouverture d’un calepin stocké dans GitHub.

Stockage local d’un calepin


Pour profiter des exemples du livre ou de tout fichier situé sur votre machine, vous devez
utiliser la fonction d’importation. Elle propose un bouton pour choisir un fichier que vous
cliquez pour accéder à une boîte standard d’ouverture de fichier dans votre navigateur.
Naviguez jusqu’à atteindre le fichier désiré, puis sélectionnez-le.
Le fait de cliquer dans cette boîte provoque le rapatriement du fichier vers Google Drive. Si
vous modifiez le fichier ensuite, la version mise à jour sera celle de Google Drive et pas celle de
votre disque local. Dans certains navigateurs Web, une nouvelle fenêtre va apparaître avec le
code source correspondant. Dans d’autres cas, vous ne verrez qu’un message de confirmation.
Dans ce cas, vous devez ensuite ouvrir le fichier comme pour n’importe quel fichier se trouvant
sur Google Drive. Il est même possible que votre navigateur vous demande si vous désirez
vraiment quitter la page actuelle. Dans ce cas, vous confirmez.
Vous pouvez récupérer un fichier vers Google Drive avec la commande Fichier/ Importer le
notebook. Le résultat est le même qu’avec la commande indiquée ci-dessus. Pour récupérer
d’autres formats de fichiers, vous préférerez cette commande Importer le notebook qui est
normalement plus rapide.

Enregistrer un calepin
Les calepins que vous créez et modifiez dans Colab peuvent être enregistrés à plusieurs
emplacements, mais pas sur votre disque local. Une fois qu’un calepin est dans Google Drive
ou dans GitHub, Colab gère le fichier dans le nuage. Pour rapatrier la plus récente version sur
votre disque local, il faut procéder au téléchargement comme nous l’expliquons dans une
section ultérieure. Découvrons les différentes options pour enregistrer un calepin sur un site
distant.

Enregistrement dans Google Drive


Par défaut, vos données sont enregistrées dans Google Drive. Lorsque vous choisissez la
commande Fichier/Enregistrer, le fichier est envoyé dans la racine de votre disque Google
Drive. Pour le stocker ailleurs, vous devez d’abord sélectionner un des sous-répertoires
(https://drive.google.com/).
Colab effectue un suivi des versions successives de votre projet, mais il ne garde qu’un certain
nombre des versions antérieures. Pour enregistrer une version qui doit subsister, utilisez la
commande Fichier/Enregistrer et épingler cette version. Vous pouvez voir la liste des
révisions disponibles avec la commande Fichier/Historique des versions (Figure 4.6).
Dans la figure, vous remarquez que le pointeur de souris s’apprête à cocher la case Épingler
de la plus récente version. Le résultat est le même qu’avec la commande précédemment
mentionnée. Historique indique la date de modification, l’auteur et la taille du fichier. Vous
pouvez sélectionner une version dans la liste pour retravailler sur une ancienne version ou
récupérer celle-ci vers votre disque local.
Figure 4.6 : Liste historique des révisions d’un projet.

Vous pouvez à tout moment créer une copie de sécurité de votre projet au moyen de la
commande Fichier/Enregistrer une copie dans Drive. La mention « copie » est ajoutée à
la fin du nom du fichier. Vous pourrez changer ce nom ensuite. Colab stocke cette copie dans le
dossier de Google Drive actuel.

Enregistrement dans le référentiel GitHub


Le site GitHub offre une autre solution pour stocker vos contenus de façon organisée. Il permet
notamment de partager du code pour en discuter, le réviser et le distribuer. L’adresse
principale de GitHub est https://github.com.
Si vous choisissez GitHub pour travailler avec Colab, n’utilisez que des référentiels à accès
public, même si GitHub reconnaît les référentiels fermés. Pour enregistrer un fichier sur
GitHub, vous utilisez la commande Fichier/Enregistrer une copie dans GitHub. Si vous
n’avez pas encore ouvert de session GitHub, Colab affiche une fenêtre qui vous demande de
vous authentifier. Vous arrivez ensuite à une boîte ressemblant à celle de la Figure 4.7.

Figure 4.7 : Stockage de vos projets dans un référentiel GitHub.

Au départ, vous n’avez pas de référentiel disponible. Vous devez soit en créer un, soit accéder
à un référentiel existant. Dès que le fichier est enregistré, il apparaîtra dans le référentiel avec
un lien pour l’ouvrir dans Colab, à moins de ne pas accepter cette option.

Enregistrement de bribes dans GitHubGist


Vous pouvez aussi partager des fichiers isolés et des ressources sur le site compagnon réservé
à l’origine aux extraits, Gist de GitHub. Certains s’en servent pour leurs projets complets, mais
l’objectif de ce site est de permettre de partager des idées de codage et des bribes de code pas
encore finalisé. Pour en savoir plus au sujet de ces galeries d’extraits appelées également
pastebin, voyez l’adresse https://help.github.com/articles/about-gists/.
Les galeries Gist peuvent être à accès public ou pas, et vous pouvez accéder aux deux
variantes depuis Colab. L’outil rend automatiquement vos fichiers secrets. Pour enregistrer
votre projet dans Gist, utilisez la commande Fichier/Enregistrer une copie en tant que
fichier Gist. À la différence de GitHub, vous n’avez pas besoin de créer un référentiel
d’abord et de créer un répertoire. Les fichiers sont enregistrés en tant que bribes. La page
correspondante proposera un lien pour visualiser le code dans Colab (Figure 4.8).
Figure 4.8 : Stockage d’un fichier ou d’une ressource sur le site Gist.

Rapatriement d’un calepin


Deux techniques sont possibles dans Colab pour récupérer localement un calepin, donc le
télécharger vers votre disque local : la commande Fichier/Télécharger le fichier .ipynb
et la commande Fichier/Télécharger le code .py qui ne conserve que le code Python.
Dans les deux cas, le fichier sera placé dans le répertoire standard de téléchargement tel que
réglé dans votre navigateur. Colab ne propose pas de choisir l’emplacement destinataire.

Principales actions Colab


La plupart des opérations que vous réalisez dans Colab se font de la même façon que dans
l’outil Notebook. Par exemple, vous créez des cellules de la même façon. Les cellules de
marquage Markdown sont disponibles en trois catégories : pour le texte, pour les titres et pour
la table des matières ou le sommaire. Certains détails sont différents pour les cellules
Markdown, mais le principe est le même. Vous pouvez aussi modifier et déplacer les cellules,
évidemment. Une énorme différence est que vous ne pouvez pas modifier le genre d’une
cellule. Si vous créez une cellule en tant que titre, vous ne pouvez pas la transformer en cellule
de code. Il faut la supprimer et la recréer. Découvrons les principales possibilités de Colab.

Création d’une cellule de code


La première cellule qui est insérée par Colab est une cellule de code, elle offre toutes les
possibilités dont vous avez l’habitude dans Notebook. Une particularité est l’apparition sur le
côté droit de la cellule d’une petite barre d’outils qui donne accès à un menu local (Figure 4.9).

Figure 4.9 : Les cellules de code dans Colab possèdent une petite barre d’outils du côté droit et un menu du côté gauche.

Voyons les options du petit menu local (non ouvert dans la Figure 4.9).
» Lien vers la cellule (Link to cell) : affiche une boîte contenant un lien avec
lequel vous pouvez accéder à une cellule du calepin. Vous pouvez copier le lien dans une
page Web ou dans un autre calepin pour offrir un accès direct. Votre destinataire verra la
totalité du calepin, mais n’aura pas besoin de chercher la cellule dont vous voulez discuter.
» Supprimer la cellule (Delete cell) : Supprime la cellule du projet.
» Supprimer l’élément de sortie (Clear output) : efface les informations affichées en
sortie d’exécution. Vous devez relancer cette exécution pour générer une nouvelle sortie.
» Afficher les éléments de sortie en mode plein écran (View Output
Fullscreen) : affiche sur l’écran la totalité de la sortie produite par l’exécution de la
cellule, ce qui est très pratique lorsque le résultat généré est volumineux ou lorsque vous
voulez mieux voir un graphique qui
a été produit. Vous sortez du mode plein écran par la touche Échap.
» Ajouter un commentaire (Add comment) : ajoute une bulle de commentaires du côté
droit de la cellule. Cela n’est pas la même chose qu’un commentaire dans le code source
qui lui est inséré dans le texte et concerne toute la cellule. Ces commentaires peuvent être
modifiés, supprimés ou résolus. Un commentaire est résolu lorsqu’il a été pris en compte et
n’est plus applicable.
» Ajouter un formulaire (Add a form) : insère du côté droit du code dans la même
cellule un formulaire qui permet d’ajouter une section graphique pour des paramètres. Les
formulaires ne sont pas affichés dans le calepin, mais ils ne vont pas empêcher la
réutilisation du même code source dans l’outil Notebook, grâce à la façon dont ils sont
stockés (pour plus de détails à leur sujet, voyez la page
https://colab.research.google.com/notebooks/forms.ipynb).

En marge gauche, vous disposez de l’icône pour exécuter la cellule de code. Dans la marge du
résultat généré, une icône permet d’effacer cette sortie (Figure 4.10).

Figure 4.10 : Une cellule de code Colab comporte des icônes en marge gauche.

Création d’une cellule texte


Les cellules texte de Colab correspondent aux cellules texte Markdown de l’outil Notebook. La
Figure 4.11 montre qu’une barre d’outils est proposée pour enrichir le format visuel du texte.
Les règles de balisage sont les mêmes, mais ces boutons simplifient le formatage. Dans
l’exemple, nous insérons directement un signe # pour un titre de premier niveau, en cliquant
une fois le bouton contenant les 2T en majuscules du côté gauche de la barre de boutons. Un
second clic fait descendre d’un niveau dans les titres. Le résultat est visible dans la zone
témoin à droite.
Comme les cellules de code, vous disposez d’un petit menu local du côté droit, avec quelques
options, notamment la création d’un lien direct vers la cellule.
En revanche, vous ne pouvez pas lancer l’exécution d’une cellule texte pour faire interpréter
les marques de format.
Figure 4.11 : La barre de formatage simplifie l’insertion des titres.

Création d’une cellule spéciale


La commande Insérer/Cellule d’en-tête de section ajoute une cellule sous la cellule
actuelle avec par défaut le niveau 1. Vous utilisez le bouton 2T pour descendre dans les
niveaux de titres. Le résultat est le même qu’en utilisant la barre de boutons que nous venons
de présenter en Figure 4.11.

Utilisation d’une table des matières


Colab génère automatiquement la table des matières à partir des cellules de titre. Pour afficher
cette table, vous utilisez le bouton avec une flèche vers la droite sur le bord gauche de la
fenêtre du navigateur. Vous voyez alors apparaître le panneau gauche (Figure 4.12).
La fonction de génération de table des matières est différente entre Notebook et Colab, et
varie un peu selon le système d’exploitation utilisé.

Figure 4.12 : Affichage de la table des matières (sommaire).

Modification d’une cellule


Comme l’outil Notebook, Colab offre un menu Édition ou Modifier (Edit) qui permet
notamment de couper, copier et coller le contenu d’une cellule. Alors que Notebook sait
fusionner et séparer des cellules, Colab permet de masquer ou d’afficher les cellules de code.
Ces différences n’ont pas d’impact réel sur les possibilités de création et d’édition du code
Python.

Déplacement d’une cellule


Les mêmes techniques que dans Notebook sont disponibles pour déplacer une cellule, sauf que
Colab ne permet que d’utiliser les boutons de la barre d’outils alors que Notebook offre les
commandes dans son menu d’édition.

L’accélération matérielle
Le code dont vous lancez l’exécution dans Colab est traité sur un serveur Google. Votre
ordinateur ou votre tablette ne fait qu’afficher le code source ainsi que les résultats qui sont
envoyés dans le navigateur. Quelle que soit la puissance matérielle dont vous disposez
localement, elle n’est pas exploitée, à moins d’activer une option à cet effet.
Vous disposez d’une option dans Colab accessible par la commande Modifier/ Paramètres du
notebook (Notebook settings). La boîte de dialogue qui apparaît (Figure 4.13) permet de
choisir le type du moteur d’exécution Python (option que vous ne modifierez pas), mais
également de sélectionner un processeur local, par exemple le processeur graphique GPU. Un
article en anglais fournit des détails à ce sujet (https://medium.com/deep-learning-
turkey/google-colab-free-gpu-tutorial-e113627b9f5d). N’activez pas cette option avant
d’avoir vérifié que votre ordinateur possède un processeur graphique de puissance suffisante.
L’accélération matérielle locale que permet Colab est en effet limitée, et peut ne pas pouvoir
être utilisée au moment où vous croyez en avoir besoin.

Figure 4.13 : Option d’accélération matérielle utilisant la puissance locale.

La même boîte de paramètres comporte une petite case à cocher qui peut s’avérer bien utile.
Elle permet de ne pas stocker les données correspondant au résultat généré par l’exécution du
code dans le fichier que vous sauvegardez. Lorsque ces résultats sont volumineux, par exemple
lorsqu’il s’agit d’éléments graphiques, la différence en taille et en temps d’enregistrement et
de rechargement peut être énorme. Le seul inconvénient est qu’il faut regénérer les sorties à
chaque rechargement du calepin.

Exécution du code
Le but de l’outil Colab est bien sûr de permettre d’exécuter le code source que vous rédigez.
Nous avons déjà découvert le bouton d’exécution en marge gauche de la cellule. D’autres
options sont disponibles dans le menu Exécution (Runtime). En voici quelques-unes :
» Exécuter la cellule sélectionnée (Running the current cell) : cette commande
a exactement le même effet que le bouton d’exécution de marge gauche.
» Exécuter les autres cellules (Running other cells) : vous disposez de plusieurs
commandes pour exécuter la cellule précédente, la cellule suivante ou l’ensemble des
cellules sélectionnées.
» Tout exécuter (Running all cells) : vous aurez parfois besoin de lancer l’exécution
en séquence de toutes les cellules de code de votre calepin au moyen de cette commande.
L’exécution reprend à partir de la première cellule de code et progresse jusqu’à la fin du
calepin. Vous pouvez interrompre l’exécution au moyen d’une commande du menu
Exécution.

La commande Gérer les sessions du même menu dresse la liste de toutes les sessions en
cours d’exécution pour votre compte Colab. Vous pouvez ainsi savoir quand vous avez exécuté
pour la dernière fois le contenu d’un calepin et combien d’espace mémoire est occupé. Le
bouton ARRÊTER (Terminate) permet d’interrompre l’exécution d’un calepin et le bouton
FERMER de quitter la boîte de dialogue pour revenir à l’édition.
Figure 4.14 : Commandes du menu d’exécution du code.

Informations sur un calepin


Dans le menu Affichage, la commande Informations sur le notebook donne accès à une
boîte (Figure 4.15) qui indique la taille actuelle du fichier correspondant, et la valeur de
certaines options ainsi que le nom de l’auteur. La taille maximale d’un calepin est rappelée.
Le lien Modifier donne accès directement aux paramètres d’accélération matérielle, décrite
plus tôt dans ce chapitre.

Figure 4.15 : Volet d’informations au sujet du fichier actuel.

Suivi de l’exécution du code


Colab effectue un suivi de toutes les exécutions passées que vous pouvez consulter avec la
commande Affichage/Historique d’exécution. Le panneau apparaît du côté droit ou sous la
fenêtre principale (Figure 4.16). Sachez que le numéro des entrées ne correspond pas à celui
présenté en marge gauche des cellules de code. Chaque exécution reçoit un numéro distinct.

Figure 4.16 : Historique d’exécution.

Partage d’un projet


Vous pouvez partager vos fichiers de projet Colab en les stockant dans un référentiel GitHub
ou comme bribes Gists GitHub. Vous disposez également de deux techniques directes :
» création d’un message partagé que vous transmettez ;
» génération d’un lien vers le code que vous transmettez.

Dans les deux cas, vous utilisez le bouton Partager dans l’angle supérieur droit de la fenêtre
Colab pour faire apparaître la boîte de la Figure 4.17.

Figure 4.17 : Boîte pour envoyer un message ou générer un lien de partage d’un calepin.

Dès que vous saisissez une adresse de messagerie ou un nom connu de l’annuaire dans le
champ Utilisateurs, un autre champ apparaît pour saisir une note. Cliquez ensuite le bouton
Envoyer. Si vous utilisez l’option Avancé, une autre boîte apparaît pour choisir quelques
options de partage.
L’angle supérieur droit de la boîte de dialogue comporte un lien Obtenir le lien de partage.
Cliquez-le pour faire générer un lien puis cliquez Copier le lien pour l’envoyer dans le
presse-papiers. Vous pouvez alors coller le lien dans un courriel ou un autre support de
communication.

L’aide de Colab
Colab propose un menu Aide qui contient une commande pour accéder aux questions les plus
fréquentes (FAQ). Il n’y a pas de système d’aide spécifique, mais vous trouverez toute l’aide
requise dans la page d’accueil déjà vue
(https://colab.research.google.com/notebooks/welcome.ipynb). Notez qu’il faut avoir
ouvert une session pour y accéder. Le menu permet aussi d’envoyer un rapport de bogue ou de
donner votre avis.
Une des commandes originales est celle nommée Rechercher des extraits de code (code
snippets). Elle donne accès dans le panneau gauche (Figure 4.18) à toute une série de blocs de
code pouvant être réutilisés moyennant quelques retouches. Vous insérez directement l’extrait
sélectionné au moyen du bouton Insérer. Chaque entrée est accompagnée d’un exemple.

Figure 4.18 : Les extraits de code peuvent faire gagner du temps de codage.

Colab bénéficie d’une bonne communauté d’utilisateurs. Vous pouvez par exemple, si vous avez
quelques connaissances en anglais, poser des questions sur le site Stack Overflow, puisque le
menu Aide offre une commande pour y accéder directement.
PARTIE 2
Plongée dans les données

DANS CETTE PARTIE :


» Les outils de Jupyter Notebook
» Exploiter des données de différentes sources
» Principes du conditionnement des données
» Reformatage des données
» Développement d’une solution globale
Chapitre 5
Découverte des outils
DANS CE CHAPITRE :
» La console Jupyter

» L’éditeur Jupyter Notebook

» Exploitation de graphiques et de multimédia

ans la première partie du livre, nous avons installé les outils permettant d’explorer la
D science des données avec le langage Python. L’heure est venue de voir plus en détail les
outils disponibles dans Anaconda afin de bien les maîtriser.

Nous allons passer en revue les deux outils principaux qui sont la console Jupyter et l’éditeur
Jupyter Notebook. Nous découvrirons quelques-unes des actions élémentaires que vous aurez
besoin de réaliser avec ces outils, et nous en découvrirons bien d’autres dans les chapitres
ultérieurs.
Le code source de tous les exemples du chapitre, comme pour tout le livre, est disponible dans
un fichier d’archive compressé. Pour ce chapitre, le fichier porte le nom PYDASC_05.ipynb.
Cependant, la quantité de texte de ce chapitre est tellement réduite que nous vous conseillons
de saisir le code source tout en suivant les explications.

La console Jupyter Console


La console correspond à une fenêtre en mode texte, qui permet d’interagir en saisissant des
instructions. Cela vous permet de tester des idées et d’en voir immédiatement les résultats. Si
vous faites une erreur, vous refermez la fenêtre de la console et en ouvrez une nouvelle. Cette
console a donc d’abord un rôle d’expérimentation.
L’outil que nous allons découvrir, IPython, constitue un sur-ensemble de la console Python
classique. Dans la suite, nous parlerons d’IPython pour la version Anaconda de Python. Si vous
avez déjà utilisé la console Python habituelle, vous ne serez pas perdu, et profiterez même de
plusieurs nouveautés. Certaines opérations se réalisant de façon différente, nous vous
conseillons de lire la suite même si vous connaissez déjà l’interpréteur Python classique.

Utilisation du mode texte


Lorsque vous démarrez la console Python, par exemple en saisissant la commande IPython
dans la fenêtre Anaconda Prompt, vous voyez apparaître une fenêtre comme celle de la
Figure 5.1.

La console Jupyter
Pour mener des expériences en sciences de données de façon interactive, vous disposez d’une
console Python, à laquelle vous accédez par la commande Anaconda Prompt. Dans cette
fenêtre en mode texte, vous pouvez lancer les commandes et voir immédiatement les résultats.
En cas d’erreur, vous refermez la fenêtre et vous en ouvrez une autre. Cette console permet de
faire des essais et de voir ce qui est possible.
Lorsque vous installez l’environnement normal du langage Python, vous disposez d’une console
Python. Celle que propose anaconda, qui correspond à la commande IPython, lui ressemble
énormément, et permet les mêmes opérations, ainsi que quelques autres en supplément. (La
version anaconda de la console Python sera nommée IPython dans la suite du texte.). Si vous
avez déjà utilisé la console standard Python, aucun mal à vous approprier IPython. Certaines
opérations ne se font pas de la même façon, par exemple pour coller un bloc de texte. Il n’est
donc pas inutile de lire la suite de cette section, même si vous connaissez la console Python
classique.

Les bases de la console IPython


Pour accéder à la fenêtre de l’interpréteur IPython, vous utilisez la commande anaconda
Prompt puis vous saisissez la commande IPython et validez par Entrée. Vous voyez quelque
chose dans le style de la Figure 5.1.

Figure 5.1 : Aspect initial de la console IPython.

Deux textes apparaissent pour rappeler le numéro de version de Python et d’Anaconda, donner
quelques premières pistes d’accès à l’aide et rappeler les droits d’auteur et la licence. Vous
pouvez par exemple saisir directement la commande credits et valider pour voir apparaître
une liste de contributeurs à ce produit.
Vous remarquez notamment une ligne qui propose de saisir le signe ? pour accéder à l’aide. Si
vous saisissez ce signe, plusieurs commandes sont proposées :
» ? : aide générale pour utiliser Jupyter.
» nom ? : donne accès à des détails concernant le paquetage, l’objet ou la méthode dont
vous mentionnez le nom avant le signe.
» nom ? ? : plus de détails au sujet du paquetage, de l’objet ou de la méthode mentionné.
» %quickref : affiche des informations au sujet d’une fonction magique de Jupyter.
» help : aide générale au sujet du langage de programmation Python. Notez que les
commandes help et ? se distinguent par le fait que la première concerne le langage
Python, alors que la seconde concerne l’outil IPython.

Si votre système d’exploitation le permet, vous pouvez cliquer-droit dans la barre de titre de la
fenêtre Anaconda Prompt pour voir apparaître un menu local. Ce menu propose un sous-menu
Modifier (ou Edit) qui procure un peu plus de confort pour votre travail en mode texte
(Figure 5.2). Ce menu comporte notamment des commandes pour récupérer la saisie et la
coller par exemple dans un calepin de Notebook.

Figure 5.2 : Menu local en mode texte qui permet de copier/ coller du texte.

N. d. T. : Ce menu local apparaîtra sur votre machine soit en anglais, soit en français. Nous
fournissons les deux versions.
Vous accédez au même menu en ouvrant le menu système du coin supérieur gauche de la
fenêtre et en choisissant Modifier. Voici les commandes les plus fréquemment proposées dans
ce menu dynamique :
» Marquer (Mark) : permet de sélectionner le bloc de texte que vous voulez copier.
» Copier (Copy) : copie le bloc marqué dans le presse-papiers. Vous pouvez également
copier en validant directement par Entrée après avoir sélectionné.
» Coller (Paste) : colle le contenu du presse-papiers dans la fenêtre à la position du
curseur. Cette commande ne fonctionne pas très bien avec la console IPython lorsqu’il y a
plusieurs lignes à copier. Mieux vaut utiliser la fonction magique de l’éditeur %paste pour
copier plusieurs lignes.
» Sélectionner tout (Select all) : sélectionne la totalité du texte visible dans la fenêtre.
» Défilement (Scroll) : permet de faire défiler le contenu de la fenêtre avec les flèches du
curseur. Frappez Entrée pour sortir de ce mode.
» Rechercher (Find) : affiche une boîte permettant de rechercher du texte dans la totalité
de la session correspondant à la fenêtre. Cette commande est très utile, car elle permet de
retrouver un mot que vous avez saisi dans la partie qui n’est plus visible de la fenêtre.

La console IPython offre par rapport à la console Python standard plusieurs améliorations, et
notamment une commande pour effacer l’écran, cls. Il suffit de saisir cette commande et de
valider par Entrée. Si vous avez besoin de réinitialiser l’interpréteur dans la fenêtre, comme si
vous deviez redémarrer le moteur de l’outil Notebook, il suffit de saisir les trois commandes
suivantes :

import IPython
app = IPython.Application.instance()
app.shell.reset()

Ce redémarrage fait revenir à zéro la numérotation, ce qui permet mieux de suivre votre
séquence d’exécution. Lorsque vous avez seulement besoin de réinitialiser toutes les variables,
utilisez la fonction magique %reset.

Personnalisation de l’aspect de la fenêtre


Sous Windows, l’aspect de la console peut facilement être personnalisé. Dans les autres
systèmes d’exploitation, vous disposez d’un jeu d’options d’interface similaire. Si rien n’est
proposé sur la plate-forme, vous aurez recours aux fonctions magiques décrites dans une
section ultérieure du chapitre. Sous Windows, ouvrez le menu Système en haut à gauche et
choisissez Propriétés pour faire apparaître la boîte de la Figure 5.3.

Figure 5.3 : Boîte des propriétés pour contrôler l’aspect de la fenêtre Anaconda Prompt.

Les options sont réparties en plusieurs pages. Les choix sont faits au niveau de la fenêtre
Anaconda Prompt, mais il s’applique à votre session IPython. Voici à quoi sert chaque des
onglets de la Figure 5.3 :
» Options : permet de contrôler la taille du curseur, un curseur plus large étant préférable
sur fond clair. Vous pouvez régler le nombre de commandes mémorisées et le mode
d’édition (mode insertion ou pas).
» Police (Font) : permet de choisir la police d’affichage du texte. Le choix le plus
confortable est en général Raster fonts. Vous pouvez choisir une autre police si elle vous
semble plus lisible en fonction de vos autres choix.
» Configuration (Layout) : permet de choisir la taille de la fenêtre, sa position à l’écran
et la taille du tampon qui va contenir le texte qui disparaît suite au défilement. Vous
pouvez aussi augmenter la taille de la fenêtre. Pour avoir accès à un historique plus long,
augmentez la taille du tampon.
» Couleurs (Colors) : sert à configurer votre palette de couleurs. La configuration
standard consistant à utiliser un fond noir avec du texte gris n’est en général pas très
lisible. Mieux vaut utiliser un fond blanc et du texte noir. C’est à vous de choisir. Voyez
aussi à ce sujet la fonction magique %colors.

Accès à l’aide Python


Personne ne peut mémoriser tous les éléments d’un langage de programmation, même les
meilleurs programmeurs. Il est donc indispensable d’avoir une aide du langage pour éviter de
devoir chercher sans cesse les noms des paquetages, des classes, des méthodes et des
propriétés.
La partie Python de la console IPython offre deux modes d’accès à l’aide : le mode aide de base
et le mode aide interactive. Vous utiliserez le premier pour vous former au langage. L’aide
interactive est idéale lorsque vous n’avez besoin que d’un rappel sur un sujet dont vous
connaissez le nom, sans perdre votre temps avec des informations sur d’autres sujets. Voyons
comment obtenir de l’aide sur le langage Python.

Entrée dans le mode aide


Saisissez la commande help() et validez par Entrée pour accéder à ce mode. Vous pouvez
alors saisir des commandes d’aide pour en apprendre plus au sujet du langage. Dans ce mode,
vous ne pouvez pas saisir de commande Python. D’ailleurs, l’invite montre la mention help>
(Figure 5.4). Cela confirme que vous êtes dans un mode particulier.

Figure 5.4 : Mode aide distingué par l’invite help>.

Saisissez le nom d’un objet ou d’une commande pour obtenir de l’aide à son sujet, en validant
par Entrée. Les commandes suivantes donnent accès à plusieurs rubriques :
» modules : dresse la liste des modules actuellement chargés. Le contenu de la liste
dépend de la version de Python, de sa configuration et de vos opérations antérieures.
Sachez que la commande peut demander un peu de temps pour présenter ses résultats qui
peuvent être assez volumineux.
» keywords : rappelle la liste des mots-clés du langage Python. Vous pouvez par exemple
saisir assert pour en savoir plus au sujet de cette commande.
» symbols : présente la liste des symboles spécifiques de Python, par exemple * pour la
multiplication, ou << pour le décalage à gauche.
» topics : présente une liste des thèmes généraux de Python, par exemple, les
conversions. Notez que les titres de rubriques apparaissent en lettres capitales.

Accès à l’aide en mode aide


Obtenir de l’aide en mode aide revient à fournir le nom d’un module, d’un mot-clé, d’un
symbole ou d’une rubrique, toujours en validant par Entrée. Notez que ce mode aide reste au
niveau du langage Python. Vous pouvez donc demander de l’aide au sujet des structures listes,
mais pas au sujet d’un objet créé par vous à partir de ce type liste et portant par exemple le
nom maListe. Vous ne pouvez bien sûr pas demander d’aide au sujet des caractéristiques
spécifiques d’IPython, comme cls.
Pour vous informer au sujet d’un élément d’un module, il faut ajouter le nom du module en
préfixe. Pour en savoir plus au sujet de la méthode version() qui est définie dans le module
sys, il faut saisir sys.version et non pas version.
Lorsque la réponse dépasse les possibilités de l’écran d’affichage, vous voyez apparaître en
base de fenêtre la mention -- More --. Pour défiler d’une seule ligne, vous utilisez la touche
Entrée et pour avancer d’un écran, la barre d’espace. Vous ne pouvez pas remonter dans
l’affichage. Pour sortir de la série d’écrans avant la fin, frappez la touche Q.

Sortie du mode aide


Vous pouvez directement frapper la touche Entrée sans rien saisir ou bien saisir la commande
quit (sans parenthèses) et valider par Entrée pour sortir du mode aide.

Accès à l’aide interactive


Lorsque vous avez besoin d’une aide ponctuelle sans entrer dans le mode aide, saisissez la
commande help(‘rubrique’). Par exemple, pour un rappel au sujet de la commande
d’affichage de texte, saisissez help(‘print’). Notez bien que le nom de la rubrique doit être
délimité par des apostrophes. Si vous les oubliez, vous obtenez une erreur.
L’aide interactive est applicable à tous les noms de modules, mots-clés et rubriques reconnus
par le langage. Vous pouvez donc saisir help(‘CONVERSIONS’) pour de l’aide au sujet des
conversions. Notez que vous devez bien distinguer les majuscules et minuscules dans l’aide
interactive. Si vous saisissez le nom de rubrique en minuscules, vous recevrez un message
comme quoi il n’y a pas d’aide à ce sujet.

Accès à l’aide d’IPython


L’aide d’IPython ne s’utilise pas comme celle de Python. Vous travaillez en effet dans
l’environnement de développement et non au niveau du langage. Pour accéder à cette aide,
vous utilisez la commande ? et validez par Entrée. Vous obtenez alors une longue liste
d’indications pour bien utiliser l’aide.
La principale technique d’accès à l’aide consiste à saisir un mot-clé en le faisant suivre par le
signe ? . Pour en savoir plus au sujet de la commande d’effacement, vous pouvez donc saisir
cls ? ou bien ? cls puis Entrée. En effet, le signe peut être placé avant ou après.
Vous disposez dans IPython d’un deuxième niveau d’aide plus détaillé. Il suffit de fournir deux
signes ? . La commande ? ? cls donne par exemple accès au code source de la commande
cls. Notez que ce second niveau d’aide n’est pas disponible lorsqu’il n’y a pas d’informations
complémentaires à fournir.
Pour sortir de l’affichage d’aide d’IPython avant la fin, utilisez la touche Q. Pour passer à
l’écran suivant, utilisez Entrée ou la barre d’espace.

Les fonctions magiques d’IPython


Enfin, vous avez la preuve que les ordinateurs sont magiques. En effet, Jupyter propose une
série de fonctions dites magiques qui permet de réaliser des choses très intéressantes dans la
console Jupyter. Voici un aperçu de ce que permettent ces fonctions magiques. Nous en
utiliserons certaines dans la suite du livre. N’hésitez pas à aller les essayer dès maintenant.

La liste des fonctions magiques


Pour commencer, l’idéal est de faire afficher la liste de toutes les fonctions au moyen de la
commande %quickref. Voyez un écran ressemble à celui de la Figure 5. 5. Prévoyez quelques
secondes d’adaptation, car le texte peut sembler difficile à lire au départ.
Figure 5.5 : Prenez le temps de parcourir la liste des fonctions magiques.

Utiliser les fonctions magiques


Les fonctions magiques ont un nom qui commence soit par un, soit par deux signes %. Les
premières fonctionnent au niveau de la ligne de commande alors que celles avec un double
signe s’appliquent au niveau de la cellule concernée. Nous reviendrons sur le concept de
cellule dans la partie de ce même chapitre consacrée à l’outil Jupyter. Sachez pour l’instant
que vous allez principalement utiliser des fonctions magiques avec un seul %.
La plupart des fonctions magiques affichent des informations d’état. Si vous saisissez par
exemple %cd, vous verrez apparaître le contenu du répertoire courant. Pour changer de
répertoire, vous utilisez la même commande en indiquant le nom du répertoire désiré à la
suite. Cette règle comporte quelques exceptions. Par exemple, la commande %cls n’a besoin
d’aucun paramètre pour effacer le contenu de la fenêtre.
La fonction magique très pratique %colors permet de personnaliser les couleurs d’affichage et
de les adapter mieux au périphérique utilisé. Parmi les options disponibles, citons NoColor
(noir et blanc), Linux (choix par défaut) ou encore LightBG (bleu et vert). À l’inverse de la
précédente, cette fonction magique doit toujours être suivie d’un paramètre. Si vous saisissez
%colors et validez, vous obtenez un message d’erreur.

Aide sur les objets


Le langage Python manipule principalement des objets. Vous ne pouvez quasiment rien faire
sans en manipuler. Il est donc tout à fait utile de savoir comment obtenir des informations au
sujet d’un des objets de votre programme. Voyons comment procéder.

Aide au sujet d’un objet


Pour obtenir des informations au sujet d’un des objets définis dans votre programme, il suffit
d’indiquer le nom de cet objet suivi d’un signe ? . Pour en savoir plus au sujet d’un objet
nommé maListe, du type liste, il suffit de saisir maListe ? et de valider. Le résultat indique le
type de cette liste, son contenu actuel au format chaîne, sa longueur et une chaîne de
documentation qui rappelle les grandes lignes de ce genre d’objet.
Une autre approche consiste à saisir help(maListe). Vous obtenez les mêmes informations
que celles correspondant au type liste de Python, mais les données sont spécifiques à l’objet
concerné. Cela vous évite de travailler en deux temps en cherchant d’abord le type de l’objet.

Affichage des caractéristiques d’un objet


La fonction dir() est souvent négligée alors qu’elle permet d’obtenir des informations utiles
au sujet des objets. Vous pouvez ainsi dresser la liste des propriétés et méthodes d’un objet
avec la commande dir(nomObjet). En partant d’une liste portant le nom maListe, vous pouvez
apprendre ce qu’il est possible de faire avec en écrivant dir(maListe) (suivi de Entrée, mais
nous ne le dirons plus). Vous verrez apparaître la liste de toutes les méthodes et propriétés
applicables à cet objet.

Aide d’IPython
IPython procure un second niveau d’aide pour vos objets grâce à sa syntaxe basée sur le signe
? . La mention maListe ? affiche pour l’objet son type, son contenu, sa longueur (empreinte
mémoire) et la chaîne documentaire (docomment ou docstring) associée qui en rappelle les
règles d’utilisation.
En faisant suivre le mot recherché de deux signes ? , vous obtenez plus de détails au sujet de
l’élément désigné. La mention maListe ? ? affiche tous les détails disponibles. Dans certains
cas, il n’y a rien de plus que dans la version résumée. Lorsque le code source de l’élément
interrogé est disponible, IPython l’affiche.

Aide et fonctions magiques %


Vous pouvez appliquer des fonctions magiques à vos noms d’objets. Elles permettent de filtrer
les résultats affichés par l’aide, comme ceci :
» %pdoc : affiche seulement la chaîne documentaire docomment pour l’objet.
» %pdef : montre comment écrire un appel à l’objet, le cas échéant.
» %source : affiche uniquement le code source de l’objet, le cas échéant.
» %file : rappelle le nom du fichier contenant le code source de l’objet.
» %pinfo : affiche les informations détaillées au sujet de l’objet.
» %pinfo2 : affiche encore plus d’informations au sujet de l’objet.

Utilisation de Jupyter Notebook


Vous utiliserez en général l’interpréteur IPython en mode texte pour faire des essais, rien de
plus. Pour construire vos projets avec plus de confort, vous profiterez de l’atelier Jupyter
Notebook, qui est un Environnement de Développement Intégré (EDI ou IDE en anglais). C’est
un des principaux composants de la suite d’outils Anaconda. Découvrons les actions de base
que vous aurez besoin de réaliser dans Jupyter Notebook. Dans la suite, nous appellerons
l’outil simplement Notebook.

Contrôle des styles des descriptions


Un des points forts de Notebook par rapport aux autres ateliers IDE est qu’il permet de faire
cohabiter des textes de description visuellement attrayants avec le code source. Vous n’êtes
plus contraint d’ajouter vos commentaires dans la même police que le code source. Vous
pouvez créer des sections et des sous-sections et améliorer l’aspect visuel grâce à des styles.
Vous obtenez au final un véritable dossier descriptif qui contient votre code exécutable. Ce
contrôle de l’aspect visuel passe par les styles.
Dans Notebook, vous saisissez le code source dans des cellules. Chaque bloc de code
correspond à une cellule distincte. Pour insérer une cellule, vous utilisez le bouton
correspondant, un signe + dans la barre d’outils (ou la commande Insérer une cellule en
dessous). Pour supprimer une cellule, vous la sélectionnez puis vous utilisez le bouton avec les
ciseaux ou la commande Couper la cellule sélectionnée. La cellule est transférée dans le
presse-papiers. Pour la supprimer totalement, vous utilisez la commande Supprimer cellule.
Fort logiquement, le type de cellule par défaut est Code. Vous pouvez ouvrir la liste de cette
mention dans la barre d’outils pour choisir parmi les autres styles proposés (Figure 5.6).

Figure 5.6 : Choix du type de cellule dans la liste de Notebook.

Le style nommé Markdown permet d’enrichir l’aspect des cellules contenant des descriptions.
Choisissez par exemple ce type dans la liste, puis saisissez le titre principal en commençant
bien par un signe # suivi d’une espace :
# Utilisation de Jupyter Notebook

Cliquez ensuite le bouton d’exécution (Run) de la barre d’outils. La cellule prend en compte la
demande d’enrichissement du format. Le signe # isolé demande à Notebook de considérer que
la suite est un titre de premier niveau. Vous remarquez que le fait de lancer l’exécution a
automatiquement inséré une nouvelle cellule présélectionnée. Vous pouvez donc directement
ajouter un sous-titre en choisissant à nouveau le type Markdown dans la liste, puis en saisissant
ceci (Figure 5.7) :

## Contrôle des styles des descriptions

Lancez ensuite l’exécution. Vous constatez que le sous-titre est présenté dans une police plus
petite que le titre principal.

Figure 5.7 : L’ajout de codes de titre et de sous-titre rend la lecture du contenu du calepin plus aisée.

Le type de cellule Markdown permet également d’insérer du contenu au format HTML, par
exemple le contenu complet d’une page Web avec ses balises HTML. Nous pouvons donc
obtenir le même titre de niveau un en insérant le texte suivant :

<h1>Utilisation de Jupyter Notebook</h1>

Dès que vous lancez l’exécution, vous voyez le résultat de l’interprétation. Le code HTML va
d’abord vous servir à ajouter des liens vers le Web. Vous pouvez même insérer ainsi une
illustration. Cette possibilité participe à l’intérêt de l’outil Notebook, qui permet bien plus que
saisir du code source.
Vous disposez enfin d’un type de format brut (RawNBConvert) que nous ne décrirons pas dans
ce livre. Il permet d’insérer du texte littéral qui ne sera jamais traité par le convertisseur de
Notebook. Ce type vous permet notamment d’exporter le contenu d’un calepin dans différents
formats (voir aussi la page https://nbconvert.readthedocs.io/en/latest/). Ce style brut
permet d’inclure du contenu dans un format spécial tel que le format Lamport TeX (LaTeX), un
langage de formatage très utilisé dans le monde scientifique pour contrôler le format visuel
des documents.

Redémarrage du noyau
Au fur et à mesure de votre saisie de code source, vous créez de nouvelles variables, vous
importez des modules et vous réalisez différentes opérations qui altèrent l’environnement. Cela
peut vous amener parfois à ne plus savoir si cet environnement est encore correct.
Pour résoudre ce problème, il suffit de redémarrer le noyau au moyen du bouton montrant une
flèche circulaire (Restart the kernel), mais pas avant d’avoir enregistré le document au
moyen de la commande Save and Checkpoint (Enregistrer avec étape), le bouton qui montre
une disquette carrée. Vous n’avez plus ensuite qu’à relancer l’exécution de la totalité du
contenu pour vérifier que tout fonctionne comme prévu.
Il arrive qu’une erreur grave provoque le blocage du noyau. Vous remarquez par exemple que
les mises à jour sont lentes ou que le contenu du calepin réagit lentement. Dans cette situation
aussi, le plus simple est de redémarrer.
Lorsque vous demandez un redémarrage, un message apparaît (Figure 5.8). Lisez bien ce
message pour ne pas perdre vos dernières retouches à la suite du redémarrage. Pensez à
toujours enregistrer le document avant de confirmer.
Figure 5.8 : Pensez à enregistrer vos documents avant de redémarrer.

Retour en arrière (checkpoints)


Il vous arrivera de faire une erreur, mais vous aurez beau chercher : il n’y a pas de bouton
Annuler dans l’outil. La solution consiste à créer des points de sauvegarde ou étapes après
chaque pas de progression significatif. Vous pouvez ainsi revenir en arrière sur les dernières
actions.
Pour créer une sauvegarde, vous disposez aussi du raccourci clavier Ctrl+S.

Pour restaurer le calepin dans un état antérieur, choisissez la commande File/ Revert to
Checkpoint (Fichier/Restaurer une étape). Vous voyez apparaître la liste de tous les points
d’arrêt disponibles. Sélectionnez-en un. Un message (Figure 5.9) vous demande de confirmer.
En effet, dès que vous validez, les informations qui ont été saisies depuis cette sauvegarde sont
perdues.

Figure 5.9 : Pour annuler une erreur, vous pouvez recharger une version antérieure de votre calepin.

Insertion de graphiques et de multimédia


Dans un certain nombre de situations, une image vaut un long discours. L’outil Notebook
combine des capacités de codage à des capacités de présentation d’informations. Vous serez
étonné de voir ce qu’il est possible de faire au niveau graphique avec cet outil. Passons en
revue quelques-unes des possibilités.

Insertion d’un diagramme ou d’une image


En feuilletant ce livre, vous avez sans doute remarqué certains calepins contenant des
graphiques et des éléments multimédias. Tous les exemples du livre utilisent des images qui
sont fournies avec le fichier d’archive. Vous pouvez par exemple utiliser la fonction magique
%matplotlib en lui fournissant un des paramètres de format ‘gtk’, ‘gtk3’, ‘inline’,
‘nbagg’, ‘osx’, ‘qt’, ‘qt4’, ‘qt5’, ‘tk’ ou ‘wx’. Chacun correspond à un format et un
mécanisme de conversion des données graphiques à afficher.
La fonction %matplotlib inline permet d’insérer un graphique dans le document. C’est ce
qui a permis d’afficher, comme dans la Figure 8.1, un graphique juste sous le code source
correspondant.

Chargement d’un exemple téléchargé


Certains des exemples visibles sur le Web sont plus faciles à étudier si vous les chargez sur
votre machine. Vous utiliserez à cet effet la fonction magique %load. Il suffit de la faire suivre
de l’adresse URL de l’exemple à faire exécuter localement, comme ceci :

%load https://matplotlib.org/_downloads/pyplot_text.py
Lancez l’exécution de la cellule, et l’exemple est chargé dans la cellule. La commande %load
est neutralisée par mise en commentaire. Vous pouvez immédiatement exécuter l’exemple pour
admirer le résultat graphique.

Récupération de graphiques en ligne


La plupart des fonctions concernant les graphiques et le multimédia sont réunies dans la
catégorie Jupyter.display. Il suffit d’importer la classe appropriée pour insérer par exemple
une image dans un calepin. Voici un exemple qui insère une des images tirées du blogue de
l’auteur dans le calepin servant d’exemple à ce chapitre :

from IPython.display import Image


Embed = Image(
‘http://blog.johnmuellerbooks.com/’ +
‘wp-content/uploads/2015/04/Layer-Hens.jpg’)
Embed

Dans la première ligne, nous demandons d’importer la classe Image puis nous utilisons des
fonctions de cette classe pour définir ce que nous voulons insérer puis pour réaliser cette
insertion. Le résultat est visible en Figure 5.10.
Si vous voulez que l’image soit mise à jour automatiquement, il faut créer un lien actif. Vous
devrez le réactualiser parce que le contenu du calepin a été importé une fois pour toutes. Pour
insérer un tel lien, vous remplacez la dernière instruction de l’exemple précédent, Embed, par
cette commande :

SoftLinked = Image(url=‘http://blog.johnmuellerbooks.com/wp-content/
uploads/2015/04/Layer-Hens.jpg‘)

Figure 5.10 : Insertion d’une image dans un calepin.

Si vous comptez insérer souvent des images, vous aurez intérêt à prédéfinir le format. Par
exemple, si vous voulez souvent insérer des éléments au format PDF, vous pouvez ajouter ces
directives au début de votre projet :

from IPython.display import set_matplotlib_formats


set_matplotlib_formats(‘pdf’, ‘svg’)

De nombreux formats graphiques sont disponibles, les plus communs étant ‘png’, ‘retina’,
‘jpeg’, ‘svg’ et ‘pdf’.
Les possibilités multimédias de Jupyter Notebook sont très riches puisque vous pouvez même
importer une vidéo en l’insérant directement dans le calepin à l’endroit où vous voulez ajouter
une description. Quelques exemples des possibilités de visualisation sont disponibles à
l’adresse suivante :

http://nbviewer.jupyter.org/github/iPython/iPython/blob/1.x/examples/
notebooks/Part%205%20-%20Rich%20Display%20System.ipynb.

Passons maintenant au travail sur les données.


Chapitre 6
Formats de données d’entrée
DANS CE CHAPITRE :
» Captation d’un flux de données

» Fichiers plats et non structurés

» Accès à une base de données

» Sources de données NoSQL

» Accès aux données sur le Web

E n choses
sciences des données, les données constituent une donnée incontournable (sic). Les
seraient plus simples si vous pouviez aller dans une boutique de données pour
acheter un bloc de données tout prêt ; vous n’auriez plus qu’à écrire l’application pour
l’analyser. En réalité, les données proviennent de sources très diverses, dans des formats très
divers, et leur interprétation est très variable. Chaque entreprise interprète ses données de
façon particulière et les stocke à sa façon. Même lorsqu’une entreprise utilise le même système
pour gérer ses données qu’une autre, il y a peu de chances que les données se présentent dans
le même format et même qu’elles utilisent les mêmes types. Autrement dit, avant de pouvoir
commencer à faire de la datalogie, vous devez d’abord découvrir comment accéder aux
données que vous cherchez, parmi tous les formats possibles. La conformation des données
d’entrée est un véritable travail, mais heureusement, Python sait répondre à vos besoins.
Dans ce chapitre, nous allons découvrir quelques techniques d’accès aux données dans
différents formats et depuis différents gisements. Les flux mémoire sont le mode d’exploitation
de données que tout ordinateur supporte dès le départ. Les fichiers plats existent tant sur vos
disques personnels qu’à distance. Les bases de données, toujours constituées de plusieurs
fichiers, sont situées en général à distance, mais il existe de petits moteurs de bases de
données (SGBD) comme Access, qui peuvent être implantés localement. Enfin, les données
Web sont évidemment accessibles via Internet. D’autres gisements existent, par exemple les
terminaux de point de vente TPV (POS, Point Of Sale), mais nous ne les aborderons pas ici. Un
livre entier pourrait être écrit au sujet des sources de données et de leur format. Les
techniques que nous allons présenter permettent de vous adapter aux formats que vous allez
rencontrer le plus souvent dans votre vie de datalogue.
La librairie d’apprentissage Scikit-learn comporte un certain nombre de jeux de données
d’essai pour l’apprentissage avec lesquels vous pouvez vous entraîner. Ils sont suffisamment
riches pour permettre d’envisager un certain nombre de tâches, et notamment faire des
expérimentations Python dans le domaine de la datalogie. Ces données sont disponibles dès le
départ, et pour ne pas travailler avec des exemples trop complexes, nous allons choisir de les
adopter dans ce livre dans de nombreux exemples. Bien que ce soit des jeux d’essai, nous
sommes en mesure de présenter à travers eux suffisamment de techniques réutilisables avec
des données réelles.
Si vous ne voulez pas saisir le code source des exemples du chapitre, utilisez le calepin
correspondant au fichier PYDASC_06. Nous avons indiqué dans l’introduction comment
récupérer le fichier archive des exemples.
Le répertoire (dossier) de travail des exemples doit contenir également les cinq fichiers de
données suivants :

» Colorblk.jpg
» Colors.txt
» Titanic.csv
» Values.xls
» XMLData.xml
Ces cinq fichiers doivent être placés dans le même sous-répertoire que les fichiers de vos
calepins Notebook. Dans le cas contraire, vous subirez des erreurs d’entrée-sortie en essayant
d’exécuter les exemples. L’emplacement de votre sous-répertoire de travail varie selon la plate-
forme. Sous Windows, vos fichiers d’exemples seront stockés dans votre répertoire de travail, C
: \Users\NomUtil\ PYDASC (si vous avez préparé l’environnement comme demandé dans le
Chapitre 3). Procédez donc à la copie des fichiers de données associées depuis le répertoire
contenant les fichiers décompressés vers votre répertoire de travail.

Téléchargement, flux et échantillons


Le lieu depuis lequel l’accès à vos données sera le plus rapide est l’espace en mémoire vive de
votre propre ordinateur. Le problème est que cet espace est compté. Dans tous les cas, vous
devez d’abord charger les données depuis un emplacement extérieur à cette mémoire pour
pouvoir les manipuler. C’est de cette façon que nous allons procéder avec les jeux d’essai de la
librairie Scikit-learn dans la suite du livre.
Pour un datalogue, les colonnes d’une base de données sont appelées caractéristiques
(features) ou variables et les différentes lignes sont des cas ou des observations (ou encore des
enregistrements). Chaque ligne regroupe les valeurs des variables des différentes colonnes, et
ce sont ces valeurs que vous exploitez.

Chargement d’un fragment de données en


mémoire
La façon de travailler avec données la plus évidente consiste à les charger en mémoire vive
(RAM). Nous avons déjà rencontré cette façon de procéder avec un jeu d’essai de la librairie
Scikit-learn. Voyons maintenant comment travailler avec un nouveau fichier, en l’occurrence
Colors.txt dont le contenu est montré dans la Figure 6.1.

Figure 6.1 : Format du contenu du fichier Colors.txt.

L’exemple va utiliser des fonctions prédéfinies de Python (fonctions standard). Quel que soit
son type, lorsque vous chargez un fichier en mémoire, toutes les données sont disponibles en
permanence, et le chargement est assez rapide si le fichier n’est pas trop volumineux. Voyons
un premier exemple d’utilisation de cette technique.

with open("Colors.txt", ‘r’) as open_file:


print(‘Contenu de Colors.txt :\n’ + open_file.read())

Nous utilisons d’abord la méthode standard nommée open() pour récupérer un objet du type
file. Cette méthode attend en paramètres le nom du fichier, puis le mode d’accès désiré qui
est ici le mode lecture, r (Read). À la fin de la deuxième ligne, nous utilisons la méthode de
lecture open_file.read() de notre nouvel objet de type fichier pour lire la totalité du
contenu. Nous aurions pu ajouter un paramètre de taille lors de l’appel à la méthode, par
exemple read(15). Dans ce cas, Python n’aurait lu que les 15 premiers caractères, ou bien
jusqu’à atteindre la fin du fichier EOF (End Of File). L’exécution de ce premier exemple affiche
ceci :

Contenu de Colors.txt:
Couleur Valeur
Rouge 1
Orange 2
Jaune 3
Vert 4
Bleu 5
Pourpre 6
Noir 7
Blanc 8

Cette opération demande de charger la totalité du contenu du fichier en mémoire vive. S’il n’y
a pas assez d’espace disponible en mémoire, le processus va évidemment échouer. Dans ce cas,
vous devrez adopter une autre technique pour accéder aux données, par exemple avec un flux
ou en réalisant un échantillonnage préalable. Autrement dit, n’utilisez pas la présente
technique si vous n’êtes pas sûr que le volume de données puisse être stocké en mémoire vive.
Ce genre de souci n’est pas à craindre avec les jeux d’essai de la librairie Scikit-learn.

Chargement progressif des données via un flux


La plupart des jeux de données que vous voudrez exploiter seront trop volumineux pour être
chargés en un seul bloc mémoire. Par ailleurs, certaines sources de données souffrent d’un
accès lent. Le mécanisme de flux (stream) répond à ces deux soucis en permettant un
chargement progressif des données. Vous ne chargez qu’une portion des données à la fois pour
les traiter au lieu d’attendre que la totalité des données soit lue. Voici un exemple d’utilisation
d’un flux de données avec Python :

with open(“Colors.txt”, ‘r’) as open_file:


for observation in open_file:
print(‘Lecture de data: ‘ + observation)

Comme dans le précédent exemple, nous partons du fichier local nommé Colors. txt. Ce
fichier contient une ligne d’en-tête puis un certain nombre d’enregistrements qui établissent
une équivalence (mapping) entre un nom de couleur et une valeur numérique. L’objet fichier
nommé open_file contient un pointeur permettant de manipuler le fichier ouvert.
La deuxième et la troisième ligne de code réalisent la lecture des données dans une boucle de
répétition for. Lors de chaque tour de boucle, le pointeur fichier désigne la ligne suivante et
chaque ligne ou enregistrement apparaît seul dans la variable observation. Il ne reste plus
qu’à afficher la valeur qui se trouve dans observation avec un appel à print(). Voici le genre
d’affichage résultant :

Lecture de data: Couleur Valeur

Lecture de data: Rouge 1

Lecture de data: Orange 2

Lecture de data: Jaune 3

Lecture de data: Vert 4

Lecture de data: Bleu 5

Lecture de data: Pourpre 6

Lecture de data: Noir 7

Lecture de data: Blanc 8

Dans cet exemple, Python ne lit qu’un seul enregistrement à la fois depuis un flux. Cela vous
oblige donc à effectuer autant d’actions de lecture qu’il y a d’enregistrements.

Échantillonnage de données
Lorsque vous chargez une source de données sous forme de flux, vous obtenez graduellement
la totalité des enregistrements, mais parfois vous n’en voulez qu’une partie. En filtrant dès
l’entrée, vous économisez du temps et des ressources. Vous pouvez par exemple demander à
ne lire que chaque cinquième enregistrement, ou en lire certains choisis de façon aléatoire.
L’exemple suivant montre comment lire uniquement les enregistrements pairs dans le fichier
d’entrée :

n = 2
with open(“Colors.txt”, ‘r’) as open_file:
for j, observation in enumerate(open_file):
if j % n == 0:
print(‘Ligne lue: ‘ + str(j) +
‘ Contenu: ‘ + observation)

N. d.T. : Comme il est de règle en programmation, la numérotation commence par le chiffre


zéro. Les enregistrements pairs correspondent donc aux indices 0, 2, 4, etc.

La différence entre cette version et la précédente est que nous utilisons la méthode
enumerate() pour récupérer le numéro de ligne. Un test utilisant la fonction modulo % permet
de vérifier si le reste de la division entre le numéro de ligne et la variable n est égal à zéro (if
j % n == 0). Si c’est le cas, nous voulons récupérer cet enregistrement et nous l’affichons.
L’exécution de l’exemple donne ceci :

Ligne lue: 0 Contenu: Couleur Valeur

Ligne lue: 2 Contenu: Orange 2

Ligne lue: 4 Contenu: Vert 4

Ligne lue: 6 Contenu: Pourpre 6

Ligne lue: 8 Contenu: Blanc 8

C’est la valeur de la variable n déclarée au début de l’exemple qui détermine le comportement


du filtrage ou échantillonnage. Donnez la valeur 3 à la variable puis relancez l’exécution.
Dorénavant, vous ne verrez apparaître que la première ligne (0), qui est celle des en-têtes, puis
les lignes 3 et 6 (les quatrième et septième).
Selon la même approche, il est possible de faire un échantillonnage au hasard. Il suffit de
donner une valeur aléatoire à la variable servant de sélecteur :

from random import random


sample_size = 0.25
with open(“Colors.txt”, ‘r’) as open_file:
for j, observation in enumerate(open_file):
if random() <= sample_size:
print(‘Ligne lue: ‘ + str(j) +
‘ Contenu: ‘ + observation)

Pour utiliser les fonctions aléatoires, il vous faut importer la classe nommée random. Elle
définit une méthode portant le même nom random() qui génère une valeur numérique entre
zéro et un. La valeur exacte est très difficilement prévisible, donc quasiment aléatoire. Notre
variable sample_size contient cette valeur qui permet de définir la taille de l’échantillon. Par
exemple, une valeur égale à 0.25 ne sélectionne que 25 % des lignes du fichier.
Le résultat reste trié dans l’ordre d’origine. Vous ne verrez pas la ligne du vert apparaître
avant celle de l’orange. En revanche, les éléments affichés changent d’une exécution à la
suivante. Voici un exemple de ce que cet exemple va afficher :

Ligne lue: 0 Contenu: Couleur Valeur

Ligne lue: 2 Contenu: Orange 2

Ligne lue: 5 Contenu: Bleu 5

Chargement des données d’une image


Vous aurez aussi besoin d’importer pour les analyser les données des images. La technique à
appliquer varie en fonction de l’emplacement du fichier d’origine et du format de l’image. De
nombreux exemples du livre vont utiliser ces techniques. Pour commencer, découvrons
comment charger une image stockée localement, afin d’obtenir des statistiques à son sujet,
puis l’afficher. C’est ce que propose l’extrait suivant :
import matplotlib.pyplot as plt
import matplotlib.image as img
%matplotlib inline

image = img.imread(“Colorblk.jpg”)
print(image.shape)
print(image.size)
plt.imshow(image)
plt.show()

L’exemple commence par demander l’importation de deux librairies matplotlib et pyplot. La


première se charge de la lecture de l’image en mémoire alors que l’autre se charge de son
affichage.
Dans la quatrième ligne, nous procédons à la lecture du contenu du fichier, puis affichons la
valeur de la propriété shape de l’image qui permet de connaître le nombre de pixels en
largeur et en hauteur ainsi que le nombre de couleurs par pixel. La Figure 6.2 confirme que
l’image mesure en pixels 100 sur 100 sur 3 de profondeur. La propriété size combine ces trois
valeurs pour donner 30 000 qui est le poids de l’image.

Figure 6.2 : Image de test mesurant 100 pixels au carré.

Nous procédons ensuite à la préparation de l’image avec imshow(), puis nous appelons
plt.show() pour afficher l’image (revoir la Figure 6.2). Ce n’est qu’une des nombreuses
méthodes disponibles pour manipuler une image dans Python en préparation de son analyse.

Accès aux données des fichiers plats


En général, les données que vous allez avoir besoin de traiter ne sont pas disponibles à
l’intérieur d’une librairie, comme c’est le cas des jeux d’essai de Scikit-learn. Les données du
monde réel se présentent dans des fichiers. Le fichier plat est celui offrant le format le plus
simple. En effet, le contenu est une liste d’entrées sous forme de lignes que vous pouvez lire
une par une pour les charger en mémoire. Comme déjà vu, vous pouvez charger tout le fichier
d’un seul coup ou travailler par étapes.
Les techniques de base proposées par Python souffrent d’une absence d’intelligence. Si le
fichier à lire contient une ligne d’en-tête, Python va la considérer comme s’il s’agissait d’une
ligne de données comme toutes les suivantes. Par ailleurs, vous ne pouvez pas sélectionner une
seule colonne s’il y en a plusieurs. Les choses deviennent bien plus simples pour traiter un
fichier plat avec la librairie pandas que nous allons découvrir dans les prochaines sections. Les
classes et méthodes que définit cette librairie savent interpréter le contenu d’un fichier plat
correctement.
Le format de fichier plat le moins sophistiqué et donc le plus simple à lire est le fichier dit
« texte brut » . Mais un fichier texte ne connaît qu’un type de données, le format chaîne de
caractères. Cela oblige donc à convertir les chiffres en valeurs numériques. Un format texte
plus évolué est le CSV (Comma Separator Value) ; il permet de mieux distinguer les types de
données, mais cela suppose plus de travail de lecture. À l’autre extrémité des variantes du
format de fichier plat, nous trouvons les formats spécifiques, par exemple le format des
classeurs de tableur Excel. Ils contiennent des éléments de format et même plusieurs jeux de
données dans le même fichier.
Découvrons tour à tour ces trois niveaux de sophistication du format de fichier plat et comment
les exploiter. Pour chaque format, nous adoptons les techniques permettant d’exploiter la
structure du contenu. Le format CSV, par exemple, sépare les champs de données par des
virgules (ou des points-virgules en français). Un fichier texte peut séparer les différents
champs par des caractères de tabulation. Enfin, un fichier Excel sépare les champs de données
de façon beaucoup plus complexe et ajoute pour chaque champ de nombreuses informations.
Dans tous les cas, il est toujours préférable de travailler avec des fichiers offrant un minimum
de structure du contenu, car les fichiers plats non structurés sont plus difficiles à exploiter.

Lecture d’un fichier texte


Parmi les nombreuses variantes de formatage d’un fichier texte, une des plus répandues
consiste à prévoir une ligne d’en-tête en première ligne, suivie d’un certain nombre de lignes
pour les enregistrements de données. Les champs dans chaque ligne sont séparés par des
tabulations. La Figure 6.1 montrait un exemple du contenu du fichier Colors.txt qui va servir
dans la suite.
Le langage Python propose dès le départ une série de méthodes pour lire un tel fichier, mais il
est plus pratique d’utiliser des librairies dédiées, par exemple la librairie pandas. Elle permet
d’utiliser des analyseurs qui savent lire les éléments de données pour en déterminer la nature,
en tenant compte du format général du fichier. Il est donc essentiel de choisir le bon analyseur.
Dans notre exemple, nous avons utilisé la méthode nommée read_table(), comme le montre
l’exemple suivant :

import pandas as pd
tabcoul = pd.io.parsers.read_table(“Colors.txt”)
print(tabcoul)

Nous commençons par importer la librairie pandas dont nous avons besoin. Nous appelons
ensuite la méthode read_table() pour charger le contenu du fichier mentionné dans la
variable portant le nom tabcoul. Il ne reste plus qu’à fichier le contenu de cette variable au
moyen de la fonction print(). Voici le résultat qui apparaît :

Couleur Valeur
0 Rouge 1
1 Orange 2
2 Jaune 3
3 Vert 4
4 Bleu 5
5 Pourpre 6
6 Noir 7
7 Blanc 8

Vous constatez que l’analyseur a correctement compris que la première ligne correspondait au
nom des colonnes. Les huit enregistrements sont ensuite numérotés de 0 à 7. La méthode
read_table() accepte d’autres paramètres pour régler le fonctionnement de l’analyseur, mais
la configuration par défaut suffit ici. Pour en savoir plus au sujet de ces paramètres, voyez la
page http://pandas.pydata.org/pandas-
docs/version/0.23.0/generated/pandas.read_table.html.

Lecture du format texte CSV


Le format CSV offre plus de richesse que le précédent, et peut même devenir assez complexe.
Ce format est défini par un standard mondial décrit dans la page
(https://tools.ietf.org/html/rfc4180). Voici les règles de format qui s’appliquent au
fichier CSV que nous allons essayer de charger :
» Une ligne d’en-tête définit les noms des champs.
» Les champs sont séparés les uns des autres par des virgules (des points-virgules en
français).
» Chaque enregistrement est séparé du suivant par un saut de ligne.
» Les chaînes de caractères sont délimitées par des guillemets droits.
» Les valeurs numériques entières et réelles sont aussi délimitées par des guillemets et
utilisent le point décimal (la virgule en français).
N. d. T. : Rappelons qu’en français, le format CSV utilise le signe point-virgule à la place de la
virgule, car celle-ci sert de séparateur décimal à la place du point décimal anglais. Avant de
traiter un fichier CSV, vérifiez si son format est anglais ou français.

La Figure 6.3 montre l’aspect de ce format pour le fichier nommé Titanic.CSV. Vous pouvez
afficher ce contenu dans n’importe quel éditeur de texte.

Figure 6.3 : Aspect brut d’un fichier CSV qui reste assez lisible.

Les tableurs comme Excel savent bien sûr importer un fichier au format CSV. La
Figure 6.4 montre le même fichier que la précédente une fois le fichier chargé dans une page
Excel.

Figure 6.4 : Un tableur tel qu’Excel sait interpréter un fichier au format CSV.

Vous constatez dans la première ligne qu’Excel a bien compris que la première ligne était celle
des en-têtes. Cela vous permet directement de lancer un tri sur les données en sélectionnant
une des colonnes dans l’en-tête. La librairie pandas sait correctement exploiter le format CSV,
comme le montre cet exemple :

import pandas as pd
titanic = pd.io.parsers.read_csv(“Titanic.csv”)
X = titanic[[‘age’]]
print(X)

Vous constatez que nous avons choisi comme analyseur read_csv() , fonction appropriée à ce
format qui permet de spécifier de nouvelles options. (Cet analyseur est détaillé à l’adresse
http://pandas.pydata.org/pandas-
docs/version/0.23.0/generated/pandas.read_csv.html). Pour sélectionner un champ, il
suffit d’indiquer son nom. L’exécution de cet exemple donne le résultat suivant en demandant
d’afficher les âges (la partie centrale du résultat a été tronquée pour économiser de la place) :

age
0 29.0000
1 0.9167
2 2.0000
3 30.0000
4 25.0000
5 48.0000
6 63.0000
...
1305 9999.0000
1306 26.5000
1307 27.0000
1308 29.0000
[1309 rows x 1 columns]

Cet affichage est facile à lire pour un humain, mais ce n’est pas le plus intéressant dans un
traitement. Vous pouvez générer une liste mieux exploitable en modifiant la troisième ligne de
l’exemple ainsi :

X = titanic[[‘age’]].values

Nous utilisons maintenant la propriété values. Le résultat devient le suivant, toujours après
suppression d’un grand nombre de lignes (suppression symbolisée par la ligne ...) :

[[29. ]
[ 0.91670001]
[ 2. ]
...
[26.5 ]
[27. ]
[29. ]]

Lecture de fichiers Excel et Microsoft Office


Les formats des fichiers des applications de la suite bureautique Microsoft Office, et
notamment d’Excel, sont très sophistiqués. Vous disposez de nombreux paramètres pour les
charger. Dans l’exemple, nous allons utiliser un fichier nommé Values.xls qui contient une
liste de sinus, de cosinus et de tangentes pour un certain nombre d’angles. La
Figure 6.5 donne un aperçu de son contenu.

Figure 6.5 : Fichier Excel d’exemple avec des valeurs numériques.

Lorsque vos données d’entrée sont dans un format de bureautique, par exemple Excel de
Microsoft Office, vous devez tenir compte d’une certaine complexité. Vous devez par exemple
dans le cas d’un fichier Excel indiquer à la librairie pandas laquelle des feuilles il faut lire, s’il y
en a plusieurs. Cela dit, vous pouvez même travailler avec plusieurs feuilles. Pour les autres
logiciels Office, vous devez fournir de nombreux détails. Vous ne pouvez pas être approximatif
avec la librairie pandas. Voici comment exploiter le contenu d’un fichier Excel nommé
Values.xls :

import pandas as pd
xls = pd.ExcelFile("Values.xls")
trig_values = xls.parse(‘Sheet1’, index_col=None, na_values=[‘NA’])
print(trig_values)

Nous commençons par demander l’importation de la librairie pandas puis créons un pointeur
vers le fichier Excel au moyen de la méthode constructeur nommée ExcelFile(). Le pointeur
est incarné par la variable xls. Nous pouvons ainsi choisir une feuille, choisir la colonne
servant d’index et préciser quoi faire avec les valeurs vides/absentes. La colonne d’index est
celle qui sert à trier les enregistrements. Si vous fournissez la valeur None, cela signifie que la
librairie doit générer un nouvel index. La méthode parse() sert à récupérer les valeurs
sélectionnées. Les options Excel pour cette méthode sont disponibles à l’adresse
https://pandas.pydata.org/pandas-
docs/stable/generated/pandas.ExcelFile.parse.html.
Il n’est pas obligatoire de travailler en deux étapes (obtention d’un pointeur sur le fichier puis
analyse des données). Voici comment combiner les deux étapes :

trig_values = pd.read_excel("Values.xls", ‘Sheet1’, index_col=None,


na_values=[‘NA’])

En général, mieux vaut travailler en deux étapes, notamment dans des fichiers Excel, car cela
vous évite notamment de fermer et d’ouvrir le fichier pour chaque opération de lecture.

Lecture de fichiers binaires


Un fichier de données est considéré comme non structuré lorsque le contenu se résume à une
séquence de bits. Le fichier ne comporte aucun marquage, ni aucune convention pour séparer
les blocs de bits les uns des autres. Si vous visionnez le fichier de façon brute, vous n’y repérez
aucune structure. Ce genre de format de fichier ne peut être exploité qu’en connaissant la
convention qui a présidé à sa construction. C’est notamment le cas de tous les fichiers
d’images. Chaque pixel d’une image peut par exemple correspondre à trois champs sur 32 bits
(pour les trois couleurs primaires). C’est à vous de savoir que chaque champ occupe 32 bits et
qu’il y en a trois par pixel. Dans certains formats, un en-tête technique existe bien au début du
fichier, mais il ne suffit pas pour savoir comment exploiter le contenu.
Nous allons voir comment exploiter un fichier d’image en utilisant une image du domaine
public, à l’adresse http://commons.wikimedia.org/wiki/Main_Page. Pour pouvoir utiliser des
images, il faut disposer de la librairie nommée Scikit-image qui réunit un groupe d’algorithmes
gratuits pour traiter les images (http://scikit-image.org/). (Il existe un tutoriel pour cette
librairie à l’adresse http://scipy-lectures.github.io/packages/scikit-image/.) Voyons
d’abord comment afficher l’image. Le code suivant peut prendre un certain temps pour
s’exécuter. L’image apparaîtra lorsque l’indicateur de traitement (dans l’onglet de page ou en
marge gauche) disparaîtra.

from skimage.io import imread


from skimage.transform import resize
from matplotlib import pyplot as plt
import matplotlib.cm as cm

example_file = (“http://upload.wikimedia.org/” +
“wikipedia/commons/7/7d/Dog_face.png”)
image = imread(example_file, as_grey=True)
plt.imshow(image, cmap=cm.gray)
plt.show()

Nous commençons par importer quatre librairies puis créons une chaîne de caractères qui
contient l’adresse du fichier image, chaîne que nous stockons dans la variable example_file.
Nous utilisons la chaîne dans l’appel à la méthode imread() avec le paramètre as_grey forcé
à la valeur True. Ce paramètre demande à Python de convertir l’image en niveaux de gris (si
elle l’est déjà, elle le reste).
L’image est alors chargée et il ne reste qu’à en effectuer le rendu pour pouvoir l’afficher, rendu
réalisé par la fonction imshow() qui exploite une table de couleurs en niveaux de gris cm.gray.
Nous affichons enfin l’image avec la fonction show() (Figure 6.6). Prévoyez de patienter
lorsque vous exécutez l’exemple pour la première fois.
Figure 6.6 : Affichage d’une image après rendu.

L’image est donc disponible en mémoire vive. Vous pouvez en apprendre plus à son sujet au
moyen de la commande suivante :

print(“data type: %s, shape: %s” %(type(image), image.shape) )

L’exécution de cette commande vous permet d’apprendre que l’image en mémoire est de type
numpy.ndarray avec des dimensions de 90 pixels sur 90. C’est donc un tableau de pixels que
vous pouvez manipuler. Vous pouvez par exemple changer les dimensions avec le code source
suivant :

image2 = image[5:70,0:70]
plt.imshow(image2, cmap=cm.gray)
plt.show()

Le tableau numpy.ndarray dans image2 est plus petit que celui de image et l’affichage sera
donc plus petit aussi (Figure 6.7).
N. d. T. : rogner une image ne permet plus de la comparer à une autre similaire si cette
dernière n’a pas eu besoin d’être rognée puisque le cadrage n’est plus identique.

Figure 6.7 : Résultats du rognage d’une image.

Pour une comparaison valable, il faut redimensionner ou retailler l’image et non la rogner.
L’exemple suivant retaille l’image pour lui donner les dimensions requises par une analyse :

image3 = resize(image2, (30, 30), mode=’symmetric’)


plt.imshow(image3, cmap=cm.gray)
print("Type de données: %s, shape: %s" %(type(image3), image3.shape))

L’exécution de l’exemple vous permet d’apprendre que l’image mesure maintenant 30 pixels
sur 30, la rendant comparable à d’autres images ayant ces dimensions.
Une fois que toutes les images possèdent la même taille, il reste à les aplatir, c’est-à-dire à
transformer le tableau en une ligne unique de données. En effet, une ligne de données ne
possède qu’une dimension, sa longueur, alors que l’image est au départ un tableau de lignes et
de colonnes de pixels. Vous ne pouvez donc pas vous en servir directement dans un jeu de
données. L’exemple suivant aplatit image3 pour produire un tableau de 900 éléments de long
sur une seule ligne, tableau stocké dans la variable image_row :

image_row = image3.flatten()
print("Type de données: %s, shape: %s" %(type(image_row), image_row.shape))

Le type n’a pas changé ; il reste numpy.ndarray. Dorénavant, vous pouvez ajouter ce tableau
de 900 éléments à une dimension dans un jeu de données pour lancer une analyse.

Bases de données (SGBDR)


Il existe plusieurs formats pour les bases de données (même un format en texte libre, AskSam).
La très grande majorité des bases utilisées dans les entreprises est de type relationnel, car
cette architecture permet de bien organiser d’énormes volumes de données complexes pour en
simplifier l’exploitation. D’ailleurs, le rôle d’un gestionnaire de bases de données est de rendre
ces données faciles à manipuler. Le but des systèmes de stockage de données est dans ce cas
de permettre une lecture et une écriture facile des données.
Le principe d’un système de gestion de base de données relationnelles (SGBDR) est de
permettre de créer, de manipuler et d’extraire des données de façon relativement simple.
Cependant, les besoins de stockage varient énormément en qualité et en quantité, selon la
plate-forme de traitement, ce qui a donné naissance à de nombreux produits dans ce secteur.
Pour un datalogue, l’existence d’un grand nombre de systèmes de gestion de bases de données
est un des problèmes à résoudre pour faire converger des données d’origines diverses en vue
de les analyser.
Ce que quasiment toutes les bases relationnelles partagent est le langage structuré
d’interrogation et de manipulation des données SQL (Structured Query Language). Ce langage
permet de gérer les données d’une base, d’extraire des données et même d’effectuer des
sélections afin de préconfigurer ces données. Dans certains cas, cela peut vous éviter d’écrire
certains traitements de préparation.
Pour accéder à une base de données, il faut établir une connexion, ce qui n’est pas chose facile.
Vous devez savoir comment vous connecter, ce qui suppose plusieurs étapes. Il faut d’abord se
connecter au moteur de la base. Les deux lignes suivantes donnent un aperçu de la façon de
procéder. Notez que vous ne pouvez pas essayer ces lignes :

from sqlalchemy import create_engine


engine = create_engine(‘sqlite:///:memory:’)

Une fois que vous avez accès à la base, vous pouvez piloter le moteur pour qu’il réalise des
actions. Le fruit d’une requête de lecture est un objet de type DataFrame contenant les
données. Pour écrire des données dans la base, vous devez d’abord créer un tel objet
DataFrame ou utiliser celui qui existe déjà. Voici quelques méthodes essentielles du langage
SQL :
» read_sql_table() : lit les données d’une table SQL vers un objet DataFrame.
» read_sql_query() : lit les données d’une base au moyen d’une requête SQL vers un
objet DataFrame.
» read_sql() : lit des données d’une table SQL ou d’une requête vers un objet DataFrame.
» DataFrame.to_sql() : écrit le contenu d’un objet DataFrame dans les tables de la base
active.

La librairie sqlalchemy est capable de faire le lien avec un certain nombre de bases SQL. Voici
les plus connues :
» SQLite
» MySQL
» PostgreSQL
» SQL Server
» Et d’autres bases relationnelles auxquelles vous pouvez vous connecter par le mécanisme
ODBC (Open Data Base Connectivity).
Les techniques que nous allons utiliser dans ce livre se servent de bases d’essai, mais restent
applicables aux bases relationnelles. Pour en savoir plus au sujet des bases relationnelles,
voyez la page https://docs.sqlalchemy.org/en/latest/core/engines.html.

Bases NoSQL
Certains projets de très grande envergure conviennent mal au modèle relationnel, car ils le
rendent trop complexe. Pour répondre à ce besoin, a été inventée l’architecture dite NoSQL
(Not Only SQL, qui n’est pas principalement basée sur le modèle SQL). Vous rencontrerez ce
genre de bases de données moins fréquemment parce qu’elles sont plus difficiles à mettre en
place et utilisent des techniques différentes qui supposent une formation complémentaire.
Elles offrent en revanche des caractéristiques uniques et répondent à des besoins de façon
optimale. L’exploitation d’une telle base reste proche de celle d’une base relationnelle :
1. Importation des fonctions pour accéder à la base de données.
2. Création d’une instance du moteur pour la base de données.
3. Lancement de requêtes grâce à ce moteur et aux fonctions d’accès à la base de
données.
En réalité, les détails vont varier d’un modèle à l’autre, et vous devrez vous procurer les
librairies de fonctions appropriées au produit concerné. Pour utiliser par exemple une base
MongoDB (https://www.mongodb.org/), vous devez vous procurer la librairie PyMongo afin
d’utiliser la classe nommée MongoClient pour créer une instance du moteur de base. Ce
moteur de MongoDB utilise énormément la fonction de recherche find() pour localiser les
données à extraire. Voici un pseudo-code qui donne un aperçu d’une session de travail avec
une base MongoDB. (Vous ne pouvez pas exécuter cet exemple dans le calepin.)

import pymongo
import pandas as pd
from pymongo import Connection
connection = Connection()
db = connection.database_name

input_data = db.collection_name
data = pd.DataFrame(list(input_data.find()))

Accès aux données du Web


De nos jours, vous aurez bien du mal à trouver une entreprise qui ne dépende pas de données
puisées sur le réseau Web. Par exemple, les services Web sont des applications permettant de
poser des questions et d’obtenir des réponses. De nombreux formats sont supportés pour les
données d’entrée.

INTERFACE API ET AUTRES ENTITÉS WEB

Les datalogues peuvent avoir besoin d’interfaces de programmation API pour avoir accès et manipuler
des données. Parfois, l’objectif de leur analyse est justement cette interface API. Nous ne présenterons
pas les API en détail dans ce livre parce que chacune est unique et que leur utilisation ne fait pas
partie du quotidien d’un spécialiste de la datalogie. Vous pouvez par exemple recourir à un produit tel
que JQuery (http://jquery.com/) pour accéder et manipuler les données de différentes façons en
relation avec une application Web. Les techniques correspondantes sont plutôt du domaine de la
création d’applications que de celui de la datalogie.
Les interfaces API peuvent constituer des sources de données dont vous pourriez avoir besoin pour
obtenir des données ou les mettre en forme. Il existe d’ailleurs de nombreuses entités correspondant
à des données et ressemblant à des interfaces API, mais qui n’apparaîtront pas dans ce livre. Par
exemple, les développeurs sous Windows peuvent créer des applications de type COM (Component
Object Model) pour générer des données vers le Web, données que vous pouvez utiliser pour vos
analyses. En fait, le nombre de sources de données possibles est énorme ; ce livre se focalise sur les
sources que vous utiliserez le plus fréquemment, et de façon la plus classique. Gardez cependant l’œil
ouvert sur les autres possibilités non décrites dans ce livre.

Une autre source d’alimentation en données correspond aux microservices. À la différence des
services Web, ceux-ci ont des objectifs très précis : chacun ne fournit qu’un seul moyen
d’émettre une requête et d’obtenir un résultat. Les avantages des microservices débordent du
cadre de ce livre, mais vous pouvez les considérer comme des services Web miniatures, et c’est
de cette manière que nous allons les envisager.
Un des formats de données Web les plus utiles à maîtriser est le format XML. Il permet le
stockage de nombreux types de données, y compris des pages Web complètes. Le fait de
travailler avec des services Web ou avec des microservices suppose d’ailleurs de traiter le
format XML en général. Voyons par un exemple comment récupérer les données d’un fichier
XML nommé XMLData.xml (Figure 6.8). Ce fichier reste volontairement simple et ne contient
que deux sous-niveaux hiérarchiques, mais XML peut en réalité en comporter beaucoup plus.

Figure 6.8 : Le format de données hiérarchique XML peut devenir assez complexe.

L’utilisation du format XML, même si le fichier est simple, peut s’avérer plus difficile que les
autres formats. Voici un exemple :

from lxml import objectify


import pandas as pd

xml = objectify.parse(open(‘XMLData.xml’))
root = xml.getroot()

df = pd.DataFrame(columns=(‘Number’, ‘String’, ‘Boolean’))

for i in range(0,4):
obj = root.getchildren()[i].getchildren()
lig = dict(zip([‘Number’, ‘String’, ‘Boolean’],
[obj[0].text, obj[1].text,
obj[2].text]))
lig_s = pd.Series(lig)
lig_s.name = i
df = df.append(lig_s)

print(df)

Après avoir importé les librairies requises, nous analysons les données avec la méthode
objectify.parse(). Un document XML doit toujours posséder un nœud racine qui est ici
incarné par <MyDataSet>. De ce nœud dérivent tous les autres nœuds du contenu, ces autres
nœuds étant des enfants. Pour pouvoir exploiter le document, il faut donc d’abord pouvoir
accéder au nœud racine avec la méthode getroot().
Nous créons ensuite un objet de type DataFrame nommé df qui dès le départ va contenir les
noms des colonnes des entrées : Number, String et Boolean pour les données XML. La
librairie pandas se base comme pour les autres formats sur un objet DataFrame. Nous entrons
ensuite dans une boucle de répétition for pour stocker dans l’objet df les quatre
enregistrements trouvés dans le fichier XML (chacun correspond à un nœud nommé
<Record>).
Le processus semble un peu complexe, mais il suit un ordre logique. La variable nommée obj
reçoit les données de tous les nœuds enfants d’un nœud <Record>. Ces nœuds sont chargés
dans un objet dictionnaire dont les clés sont les valeurs Number, String et Boolean, ce qui
correspond aux colonnes de l’objet DataFrame.
Nous disposons à partir de ce moment d’un objet dictionnaire contenant les données des
lignes. Nous créons alors une ligne avec les données réelles pour DataFrame. La ligne reçoit la
valeur disponible dans le tour de boucle for en cours, puis cette ligne est ajoutée à
DataFrame. Nous demandons d’afficher le résultat pour confirmer que tout s’est passé comme
prévu :

Number String Boolean


0 1 Primo True
1 2 Secundo False
2 3 Tercio True
3 4 Quarto False

L’ALTERNATIVE JSON

Le format XML est très répandu sur le Web, mais ce n’est pas le seul. Un autre format assez répandu
qu’il faut prévoir dans vos projets est le format JSON ou JavaScript Objet Notation
(http://www.json.org/). Les promoteurs de JSON prétendent qu’il est moins volumineux, plus facile
à utiliser et plus performant que le format XML. Il vous arrivera de recevoir des données au format
JSON en entrée au lieu de XML pour travailler avec certains services Web et microservices.
Vos stratégies d’exploitation des données seront assez simples si vous vous limitez aux deux formats
XML et JSON. Cependant, d’autres propositions de formats de données existent, pour simplifier
l’analyse. De nos jours, les développeurs cherchent à jouir d’une meilleure compréhension des flux de
données, et optent donc pour des formats privilégiant leur lisibilité par les humains. Une des
propositions les plus remarquables correspond au langage YAML
(http://yaml.org/spec/1.2/spec.html). Si vous optez pour une alternative, soyez prêt à un certain
surcroît de travail au départ, le temps de résoudre les besoins particuliers de vos nouveaux projets.
Chapitre 7
Préparation des données
DANS CE CHAPITRE :
» Les librairies NumPy et pandas

» Variables symboliques

» Gestion des formats date

» Correction des données manquantes

» Découpage, fusion et modification des éléments de données

L es
particularités de vos données, et notamment leur type, constituent leur profil. C’est le
profil qui détermine quel genre de traitement vous pouvez leur appliquer. Pour que les
données soient compatibles avec un certain type d’analyse, il faut éventuellement les
reprofiler. C’est un peu comme si les données étaient de l’argile et que vous étiez un potier. Au
lieu d’utiliser vos mains pour leur donner un nouveau profil, vous utilisez des fonctions et des
algorithmes. Découvrons dans ce chapitre les outils disponibles pour contrôler le profil des
données et leur impact.
Nous verrons en effet les problèmes liés au profilage. Vous devez par exemple absolument
savoir s’il manque des données dans un jeu. Si les données n’ont pas le profil adéquat,
l’analyse qui en résulte n’aura aucun sens. Certains types de données, et notamment les dates
et les heures, peuvent poser problème. Vous devez donc soigner cette préparation pour être
certain d’obtenir les résultats attendus, et aboutir à un jeu de données sur lequel un grand
nombre d’analyses pourront être menées.
Certaines opérations de profilage ont pour but de créer un jeu de données plus vaste. En
général, les données à traiter ne sont pas toutes dans la même base de données, ni toutes dans
le même format. C’est pourquoi vous devez les profiler et les fusionner pour obtenir au final un
seul jeu de données dans un format connu, et le rendre analysable. Faire converger les
données d’entrée peut ressembler à un art, car les données défient parfois votre sagacité, et
résistent aux retouches trop simples.
Le code source des exemples de ce chapitre se trouve dans le fichier PYDASC_07 du fichier
archive des exemples. Nous avons indiqué comment récupérer ce fichier dans l’introduction.

En plus du fichier de calepin, vous devez pour ce chapitre pouvoir utiliser le fichier de données
d’entrée nommé XMLData2.xml. Le fichier doit se situer dans le même répertoire que le fichier
des exemples. Dans le cas contraire, vous subirez une erreur d’entrée-sortie. Nous avons
expliqué au début du chapitre précédent comment implanter les fichiers de données dans le
répertoire.

Jonglage entre NumPy et pandas


Rappelons tout d’abord que vous aurez besoin de la librairie NumPy en permanence.
D’ailleurs, la librairie pandas se base sur NumPy. Pour autant, certaines tâches vous
demanderont de choisir de les réaliser avec l’une ou l’autre des librairies. Les tâches de bas
niveau seront réalisées avec des fonctions de NumPy, alors que pandas sera privilégiée dès
qu’elle est utilisable, car elle fait gagner du temps. Voyons plus en détail quand choisir l’une ou
l’autre des librairies.

Domaines d’utilisation de NumPy


Puisque la librairie pandas a été conçue en exploitant NumPy, toutes les opérations réalisées
avec pandas passent par la couche fonctionnelle NumPy sous-jacente. Le confort d’utilisation
des fonctions de pandas se paye parfois en performances. Certains testeurs prétendent que le
traitement est cent fois moins rapide qu’en utilisant NumPy pour la même tâche (voir aussi à
ce sujet http://penandpants.com/2014/09/05/performance-of-pandas-series-vs-numpy-
arrays/. La puissance offerte de nos jours par les ordinateurs est telle que cette pénalité au
niveau des performances devient moins critique. Cela dit, dès que les performances sont
essentielles, il vaudra toujours mieux utiliser NumPy.

Domaines d’utilisation de pandas


La librairie pandas permet d’écrire votre code plus facilement et plus vite. Les fonctions
réunies dans cette librairie sont d’un niveau tellement élevé que pandas peut vous éviter un
certain nombre d’erreurs d’écriture. L’essentiel est que la librairie pandas fournit un jeu de
fonctions couvrant la gestion du temps, l’alignement des données, les statistiques avec gestion
des manques NA-friendly, le groupage par critères, les fusions et les jointures. Si vous utilisez
NumPy, c’est à vous d’écrire toutes les fonctions équivalentes, ce qui revient tout de même à
réinventer la roue.
Dans la suite du livre, vous découvrirez les nombreux domaines d’emploi intéressant de
pandas : prétraitement des données pour réduire les effets des erreurs d’observation, appelé
aussi binning ou regroupement, exploitation d’un cadre de données DataFrame (structure
nommée en deux dimensions basée sur des colonnes pouvant contenir des types de données
différents, et permettant d’effectuer des statistiques). Le Chapitre 9 montre comment effectuer
les discrétisations et les regroupements binning. Le Chapitre 13 propose des exemples réels de
classement par binning, par exemple pour calculer la fréquence de chaque variable de
catégorie d’un jeu de données. D’ailleurs, la plupart des exemples de ce Chapitre 13 ne
pourraient pas fonctionner sans la technique de classement binning. Pour l’instant, ne
cherchez pas trop à savoir à quoi correspond cette opération et quels sont ses domaines
d’emploi. Il nous suffit de savoir que la librairie pandas simplifie énormément votre tâche.

TOUT EST DANS LA PRÉPARATION

Dans les chapitres précédents, nous avons passé beaucoup plus de temps à préparer les données
qu’à effectuer des analyses. Il est vrai que l’essentiel du temps d’un datalogue est consacré à la
préparation des données, car les données d’entrée se présentent rarement dans un format permettant
de leur appliquer immédiatement une analyse. Voici les cinq étapes à prévoir pour cette préparation :
» Obtention des données d’entrée
» Agrégation des données
» Création de sous-ensembles de données
» Nettoyage des données
» Constitution d’un jeu de données unique par fusion de plusieurs jeux

Ne vous découragez pas à l’idée de devoir progresser laborieusement à travers ces étapes. Grâce à
Python et aux différentes librairies, la préparation sera simple et efficace, et c’est pour cette raison
que nous consacrons tant de pages à cette partie initiale du travail. Mieux vous saurez comment
exploiter Python pour parcourir ces tâches initiales très répétitives, plus vite vous pourrez commencer
à vous faire plaisir en lançant toutes sortes d’analyse.

Validation des données


Au départ, il est difficile de savoir ce que contient exactement une grosse base de données.
Vous pouvez en avoir un aperçu, mais lorsqu’il s’agit de considérer la base globalement, c’est
physiquement impossible. Et en ne sachant pas le contenu exact, vous ne pouvez pas garantir
que les analyses vont produire des résultats pertinents. C’est pour cette raison que vous devez
valider vos données afin de les placer dans un état proche de celui dont vous désirez disposer.
Vous devrez par exemple supprimer les enregistrements en double pour ne pas fausser les
analyses (sauf cas particulier).
Notez que vous devez rester proactif. Les opérations de validation ne vont pas garantir
l’exactitude des données, ni empêcher que des valeurs se situent en dehors d’une plage
estimée. Nous verrons dans le prochain chapitre comment gérer ces situations. Ce que garantit
la validation est que vous pourrez faire l’analyse avec de fortes chances d’arriver à vos fins.
Vous devrez peut-être ensuite revenir sur le profilage des données afin de pouvoir réaliser le
traitement prévu au départ.
Étude des contenus d’entrée
Il est important de pouvoir faire vérifier le contenu parce que le contrôle manuel devient
impossible dès qu’il y a un grand nombre d’observations et de variables. De plus, vérifier à la
main prend du temps, entraîne des erreurs et, devient vite très lassant. Pour le cas des
doublons, leur traitement est important pour deux raisons :
» Le traitement des doublons ou duplicatas consomme du temps de traitement, et ralentit
donc votre travail.
» Les doublons faussent les résultats de par leur présence. Dès que la même entrée
apparaît plusieurs fois, l’algorithme considère qu’il est plus important que les autres.

En tant que datalogue, vos données doivent vous inspirer. Vous devez parvenir à les faire parler
en exploitant les possibilités de la librairie pandas, comme le montre cet exemple :

from lxml import objectify


import pandas as pd

xml = objectify.parse(open(‘XMLData2.xml’))
root = xml.getroot()
df = pd.DataFrame(columns=(‘Number’, ‘String’, ‘Boolean’))
for i in range(0,4):
obj = root.getchildren()[i].getchildren()
row = dict(zip([‘Number’, ‘String’, ‘Boolean’],
[obj[0].text, obj[1].text,
obj[2].text]))
row_s = pd.Series(row)
row_s.name = i
df = df.append(row_s)

search = pd.DataFrame.duplicated(df)
print(df)
print()
print(search[search == True])

L’exemple montre comment trouver les doublons. Il utilise comme données d’entrée une
version modifiée du fichier XML déjà vu, XMLData2.xml. Cette variante contient un
enregistrement en double. En situation réelle, il y a des milliers et des milliers
d’enregistrements, avec éventuellement des centaines de doublons. Cet exemple suffit à
montrer le principe. Il commence par charger le fichier de données en mémoire (comme déjà
vu dans le Chapitre 6), puis il copie les données dans un objet DataFrame.
Au départ, les données peuvent être considérées comme défectueuses, puisqu’elles
contiennent des doublons. Pour les éliminer, il faut d’abord les localiser. Nous commençons par
créer un objet de recherche contenant la liste des lignes en double au moyen d’un appel à
pd.DataFrame.duplicated(). Toutes les lignes en double ont une mention True en dernière
colonne.
Dans cette situation intermédiaire, vous êtes face à une liste de lignes mélangeant doublons et
non-doublons. Pour repérer les lignes en double, nous créons un index qui utilise comme
critère l’expression search == True. Voici le résultat affiché par l’exemple. Vous constatez que
la ligne numéro trois (la quatrième) est un doublon dans la sortie de DataFrame et que cette
ligne est bien affichée par la fonction de recherche :

Number String Boolean


0 1 Primo True
1 2 Secundo False
2 3 Tercio True
3 3 Tercio True

3 True
dtype: bool

Suppression d’un doublon


Pour nettoyer le jeu de données, nous allons supprimer les doublons en profitant d’une fonction
prédéfinie par la librairie pandas. Étudions cet exemple :
from lxml import objectify
import pandas as pd

xml = objectify.parse(open(‘XMLData2.xml’))
root = xml.getroot()
df = pd.DataFrame(columns=(‘Number’, ‘String’, ‘Boolean’))
for i in range(0,4):
obj = root.getchildren()[i].getchildren()
row = dict(zip([‘Number’, ‘String’, ‘Boolean’],
[obj[0].text, obj[1].text,
obj[2].text]))
row_s = pd.Series(row)
row_s.name = i
df = df.append(row_s)

print(df.drop_duplicates())

Comme dans l’exemple précédent, nous commençons par créer l’objet cadre de données df
qui contient le doublon. Pour le supprimer, il suffit d’appeler la méthode drop_duplicates().
Voici le résultat :

Number String Boolean


0 1 Primo True
1 2 Secundo False
2 3 Tercio True

Création d’une data map et d’un plan de données


Il est essentiel de disposer d’informations au sujet de votre jeu de données, dans le sens
statique. Une data map (carte de données) donne un aperçu global et permet de détecter les
problèmes potentiels, parmi lesquels :
» des variables redondantes ;
» des erreurs ;
» des valeurs manquantes ;
» des transformations de variable.

La recherche de ces problèmes est l’occasion d’établir un plan de données, c’est-à-dire une
liste des tâches à réaliser pour garantir l’intégrité des données. Dans l’exemple suivant, nous
disposons d’une data map avec deux jeux de données B et C :

import pandas as pd
pd.set_option(‘display.width’, 55)

df = pd.DataFrame({‘A’: [0,0,0,0,0,1,1],
‘B’: [1,2,3,5,4,2,5],
‘C’: [5,3,4,1,1,2,3]})

a_group_desc = df.groupby(‘A’).describe()
print(a_group_desc)

La data map utilise des 0 pour les premières séries et des 1 pour les secondes séries. Grâce à
la fonction groupby(), nous stockons les jeux de données B et C dans des groupes. Afin de
savoir si la data map est utilisable, nous demandons des statistiques par describe(). Le
résultat est un jeu de données B avec les séries 0 et 1, ainsi qu’un jeu de données C également
avec les séries 0 et 1.

B \
count mean std min 25% 50% 75% max
A
0 5.0 3.0 1.581139 1.0 2.00 3.0 4.00 5.0
1 2.0 3.5 2.121320 2.0 2.75 3.5 4.25 5.0

C
count mean std min 25% 50% 75% max
A
0 5.0 2.8 1.788854 1.0 1.00 3.0 4.00 5.0
1 2.0 2.5 0.707107 2.0 2.25 2.5 2.75 3.0

Nous disposons ainsi de statistiques au sujet des deux séries de jeux de données. La
distribution des deux jeux grâce aux cas particuliers constitue le plan de données. Si vous
observez les valeurs, vous pouvez déjà prévoir que ce plan n’est peut-être pas exploitable parce
que certaines valeurs sont très éloignées des autres.
Le mode de deux générations par défaut de describe() consiste à présenter les données de
manière non empilée, ce qui risque d’afficher avec des sauts de ligne, rendant le résultat
difficile à lire. Pour éviter cela, il suffit de spécifier la largeur d’affichage à utiliser au minimum
par le genre d’appel suivant :

pd.set_option(‘display.width’, 55)

Un certain nombre de paramètres de pandas peuvent être réglés ainsi (voyez à ce sujet
https://pandas.pydata.org/pandas-docs/stable/generated/pandas.set_option.html).
Dans notre exemple simple, les données restent assez lisibles même non empilées. Voyons ce
qu’il en est si nous demandons leur empilement au moyen du code suivant :

stacked = a_group_desc.stack()
print(stacked)

Le fait d’appeler la méthode stack() reformate la sortie pour la rendre plus compacte :

B C
A
0 count 5.000000 5.000000
mean 3.000000 2.800000
std 1.581139 1.788854
min 1.000000 1.000000
25% 2.000000 1.000000
50% 3.000000 3.000000
75% 4.000000 4.000000
max 5.000000 5.000000
1 count 2.000000 2.000000
mean 3.500000 2.500000
std 2.121320 0.707107
min 2.000000 2.000000
25% 2.750000 2.250000
50% 3.500000 2.500000
75% 4.250000 2.750000
max 5.000000 3.000000

Vous n’aurez pas toujours besoin de la totalité des données fournies par describe(). Par
exemple, vous pouvez vous contenter du nombre d’éléments dans chaque série et de la
moyenne. Voici comment réduire la quantité d’informations affichée :

print(a_group_desc.loc[:,(slice(None),[‘count’,’mean’]),])

En utilisant loc(), vous pouvez spécifier certaines colonnes. Voici le dernier affichage de notre
exemple qui se limite aux informations dont vous avez absolument besoin pour prendre une
décision :

B C
count mean count mean
A
0 5.0 3.0 5.0 2.8
1 2.0 3.5 2.0 2.5

Utilisation de variables catégorielles


Dans le domaine de la science des données, ce que l’on appelle une variable catégorielle est
une variable qui ne peut prendre qu’une des valeurs d’un ensemble de valeurs borné. Les
programmeurs utilisent le concept d’énumération. Chacune des valeurs que peut prendre la
variable catégorielle correspond à un niveau (level).
Pour illustrer ce concept, supposons une variable qui sert à exprimer la couleur d’un objet, par
exemple une voiture. Le client peut choisir entre bleu, rouge ou vert. Ce sont les trois niveaux
possibles de la variable catégorielle. Par ailleurs, un ordinateur préfère les chiffres aux lettres.
L’application va donc associer une valeur numérique à chacune des couleurs, par exemple bleu
vaudra 1, rouge vaudra 2 et vert vaudra 3. Si vous demandez l’affichage des couleurs par
l’ordinateur, il va vous montrer les valeurs numériques et non les noms des couleurs.
Heureusement, vous pouvez voir les vraies valeurs (les noms des couleurs) au moyen d’un
objet DataFrame de la librairie pandas. Vous devez pouvoir changer le nom d’une couleur et
même combiner des valeurs pour produire de nouveaux symboles. Les variables symboliques
servent à représenter des données exprimant des qualités pour en optimiser le stockage.

CONTRÔLE DE LA VERSION DE PANDAS

Les exemples concernant les variables catégorielles que nous allons découvrir ne fonctionnent que si
vous disposez au minimum de la version 0.23.0 de la librairie pandas. Cependant, la version
d’Anaconda mise en place peut avoir entraîné l’installation d’une version plus ancienne. Pour en avoir
le cœur net, procédez ainsi :

import pandas as pd
print(pd.__version__)

Le numéro de version de pandas doit apparaître. Vous pouvez également connaître le numéro de
version en ouvrant la fenêtre Anaconda Prompt puis en saisissant la commande pip show pandas
sans oublier de valider par Entrée. Si vous constatez que vous avez une trop ancienne version, dans
la même fenêtre, saisissez cette commande :

pip install pandas --upgrade

La mise à jour est réalisée automatiquement avec un contrôle de tous les paquetages concernés.
Notez que sous Windows, il vous faudra peut-être ouvrir la fenêtre de terminal Anaconda Prompt en
mode administrateur. Pour ce faire, cliquez-droit dans l’entrée du menu Démarrer d’Anaconda Prompt
pour choisir Exécuter en tant qu’administrateur dans le menu local.

Si vous utilisez des variables catégorielles pour l’apprentissage machine (mécapprentissage),


vous devez bien choisir l’algorithme qui va servir à manipuler les variables. En effet, certains
algorithmes, notamment ceux utilisant une approche d’arborescence, savent traiter
directement les valeurs numériques associées aux symboles alors que d’autres vous demandent
de coder les valeurs catégorielles en variables binaires, notamment les algorithmes linéaires, à
régression logistique et SVM. Si nous partons des trois niveaux d’une variable pour les
couleurs rouge, vert et bleu, il faut dans ce cas créer trois variables binaires :
» une pour le bleu (valant un si la valeur est bleue, et zéro sinon) ;
» une pour le rouge ;
» une pour le vert.

Création de variables catégorielles


Les variables catégorielles sont indispensables dans un certain nombre de traitements en
datalogie. Vous pouvez par exemple trouver ainsi toutes les valeurs qui ne font pas partie d’un
ensemble. Dans l’exemple suivant, nous créons une variable catégorielle puis l’utilisons pour
voir quelles données restent dans des limites prédéfinies :

import pandas as pd
car_colors = pd.Series([‘Bleu’, ‘Rouge’, ‘Vert’],
dtype=’category’)
car_data = pd.Series(
pd.Categorical(
[‘Jaune’, ‘Vert’, ‘Rouge’, ‘Bleu’, ‘Pourpre’],
categories=car_colors, ordered=False))
find_entries = pd.isnull(car_data)
print(car_colors)
print()
print(car_data)
print()
print(find_entries[find_entries == True])

Nous commençons par créer la variable portant le nom car_colors et contenant les trois
couleurs de base disponibles pour le modèle de voiture concerné. Notez l’obligation d’ajouter
la valeur de la propriété dtype qui vaut category.
Nous définissons ensuite une seconde série correspondant à des données d’entrée. Elle
contient une liste de couleurs car_data, mais toutes les couleurs ne sont pas définies dans la
première série (ce ne sont pas des valeurs acceptables). Lorsque pandas détecte une telle
valeur hors limites, l’affichage de la valeur est remplacé par la mention NaN (Not a Number).
N. d. T. : L’expression NaN est tout à fait justifiée dans le cadre des variables quantitatives,
c’est-à-dire des valeurs numériques. Pour des variables catégorielles, l’expression n’est pas
tout à fait exacte. Considérez que dans ce cas, l’abréviation signifie Non acceptable.

Il est bien préférable de laisser la librairie pandas chercher les couleurs non acceptables que
de le faire à la main. Vous demandez à pandas de chercher les entrées incorrectes par
is_null() et de stocker le résultat dans find_entries. Il suffit ensuite d’afficher la liste des
entrées refusées (valeur NaN). Voici les trois opérations d’affichage :

0 Bleu
1 Rouge
2 Vert
dtype: category
Categories (3, object): [Bleu, Rouge, Vert]

0 NaN
1 Vert
2 Rouge
3 Bleu
4 NaN
dtype: category
Categories (3, object): [Bleu, Rouge, Vert]

0 True
4 True
dtype: bool

Lorsque vous lisez la deuxième liste ci-dessus, vous constatez que les entrées de
rang 0 et 4 valent NaN. L’affichage du contenu de find_entries le prouve. Lorsque le jeu de
données devient volumineux, cette approche permet de repérer facilement puis de corriger les
valeurs aberrantes, avant même de tenter de leur appliquer une analyse.

Renommage des niveaux


Il arrive que les noms des valeurs ou des catégories d’une variable catégorielle conviennent
mal au besoin. Vous pouvez facilement renommer les catégories comme le montre l’exemple
suivant.

import pandas as pd
car_colors = pd.Series([‘Bleu’, ‘Rouge’, ‘Vert’],
dtype=’category’)
car_data = pd.Series(
pd.Categorical(
[‘Bleu’, ‘Vert’, ‘Rouge’, ‘Bleu’, ‘Rouge’],
categories=car_colors, ordered=False))

car_colors.cat.categories = ["Pourpre", "Jaune", "Mauve"]


car_data.cat.categories = car_colors

print(car_data)

Il suffit de donner à la propriété nommée cat.categories les nouvelles valeurs désirées. Voici
le résultat affiché :

0 Pourpre
1 Mauve
2 Jaune
3 Pourpre
4 Jaune
dtype: category
Categories (3, object): [Pourpre, Jaune, Mauve]

Combinaisons de niveaux
Il arrive que le niveau de détail offert par une catégorie soit trop important pour donner lieu à
une analyse intéressante. Il peut s’agir d’un nombre de valeurs trop faibles, ce qui ne donne
pas assez de différences au niveau des statistiques. Il est dans ce cas intéressant de combiner
plusieurs petites catégories comme le montre l’exemple suivant :

import pandas as pd

car_colors = pd.Series([‘Bleu’, ‘Rouge’, ‘Vert’],


dtype=’category’)
car_data = pd.Series(
pd.Categorical(
[‘Bleu’, ‘Vert’, ‘Rouge’, ‘Vert’, ‘Rouge’, ‘Vert’],
categories=car_colors, ordered=False))

car_data = car_data.cat.set_categories(
["Bleu", "Rouge", "Vert", "Bleu_Rouge"])
print(car_data.loc[car_data.isin([‘Rouge’])])
car_data.loc[car_data.isin([‘Rouge’])] = ‘Bleu_Rouge’
car_data.loc[car_data.isin([‘Bleu’])] = ‘Bleu_Rouge’

car_data = car_data.cat.set_categories(
["Vert", "Bleu_Rouge"])

print()
print(car_data)

Dans l’exemple, le jeu de données d’entrée car_data ne contient qu’une occurrence du bleu et
du rouge, mais trois du vert qui est donc majoritaire. Pour combiner bleu et rouge, il faut
travailler en plusieurs étapes. Tout d’abord, nous ajoutons la nouvelle catégorie Bleu_Rouge
dans car_data puis nous renommons les catégories Rouge et Bleu en Bleu_Rouge afin
d’obtenir la catégorie combinée. Pour terminer, nous supprimons les catégories de départ.
Le problème est qu’il faut d’abord localiser les entrées Rouge pour en faire des entrées
Bleu_Rouge. Pour y parvenir, nous combinons un appel à isin() pour tester si l’entrée est
rouge avec loc() qui récupère l’index de l’entrée. La première instruction d’affichage
print() montre le résultat de la combinaison. La seconde affiche le nouveau contenu de la
catégorie.

2 Rouge
4 Rouge
dtype: category
Categories (4, object): [Bleu, Rouge, Vert, Bleu_Rouge]

0 Bleu_Rouge
1 Vert
2 Bleu_Rouge
3 Vert
4 Bleu_Rouge
5 Vert
dtype: category
Categories (2, object): [Vert, Bleu_Rouge]

Dorénavant, nous avons trois entrées Bleu_Rouge et trois entrées Vert. Les catégories Bleu
et Rouge ne sont plus utilisées. Nous avons donc bien réussi à combiner deux niveaux.

Gestion des données temporelles


Les données de date méritent un traitement spécifique. La première raison est que ces
données sont stockées sous forme de valeurs numériques mais l’interprétation de ces valeurs
dépend de la convention en vigueur dans le système d’exploitation concerné, et même des
préférences définies par chaque utilisateur. Par exemple, le logiciel Excel permet de choisir de
faire démarrer les dates en 1900 ou en 1904. Le codage numérique qui en découle sera
différent : la même date peut correspondre à deux valeurs numériques différentes.
Les données de type heure posent un autre problème. Il est tout d’abord malaisé de garantir la
signification des données pour ceux qui les utilisent. Dans certains cas, vous aurez besoin
d’utiliser l’heure GMT de Greenwich, dans d’autres l’heure du fuseau horaire local. Le passage
d’une heure d’un format vers un autre n’est pas simple non plus. Voyons quelques techniques
permettant de se libérer de ces soucis.

Formatage des dates et des heures


Vous simplifierez grandement vos analyses en choisissant une représentation appropriée de
vos dates et heures. Vous devez par exemple changer de représentation pour que l’ordre de tri
des valeurs soit correct. Le langage Python propose deux techniques pour contrôler le format
de ces valeurs. La première consiste à utiliser la méthode str() qui convertit une valeur
depuis le type datetime via une chaîne de caractères brute. La fonction strftime() est plus
complexe parce qu’elle demande de définir l’aspect que doit prendre la valeur après la
conversion. Avec cette seconde fonction, il faut fournir en entrée une chaîne de formatage
(contenant des opérateurs pour décider du format). Les différents opérateurs disponibles sont
fournis à l’adresse http://strftime.org/.
L’exemple suivant commence par créer un objet du type datetime puis le convertit en une
chaîne, de deux façons différentes :

import datetime as dt

now = dt.datetime.now()

print(str(now))
print(now.strftime(‘%a, %d %B %Y’))

Vous constatez que la technique la plus simple est celle basée sur str(). Le résultat affiché
montre néanmoins que ce n’est pas nécessairement le but espéré. Vous disposez de bien plus
de souplesse en utilisant strftime().

2019-09-22 09:46:43.562838
Sun, 22 September 2019

Conversion correcte des heures


Le jonglage avec les fuseaux horaires et la gestion des heures d’été et heures d’hiver peut
créer toutes sortes de problèmes d’analyse. Vous aurez donc dans certains cas à effectuer des
décalages pour obtenir les bons résultats. Il y a donc lieu d’apprendre à convertir des heures.
L’exemple suivant montre trois techniques applicables :

import datetime as dt

now = dt.datetime.now()
timevalue = now + dt.timedelta(hours=2)

print(now.strftime(‘%H:%M:%S’))
print(timevalue.strftime(‘%H:%M:%S’))
print(timevalue - now)

La conversion d’une heure dans une autre est simplifiée par la fonction time-delta(). Voici les
différents paramètres qu’elle accepte pour convertir une valeur de date et heure :
» days
» seconds
» microseconds
» milliseconds
» minutes
» hours
» weeks

Vous pouvez facilement ajouter ou soustraire des heures, par exemple pour savoir combien
d’heures séparent deux dates. Voici le résultat affiché par l’exemple :

09:01:11
11:01:11
2:00:00

Dans l’exemple, la fonction now() permet de récupérer l’heure locale actuelle et la variable
timevalue demande un décalage de deux fuseaux horaires.

Gestion des données manquantes


Il vous arrivera de recevoir un ensemble de données comportant des trous, c’est-à-dire des
données manquantes. Par exemple, il pourra manquer l’âge pour l’enregistrement d’un client.
Si les manques sont suffisamment nombreux, les analyses que vous allez réaliser seront
faussées. Il est donc indispensable d’adopter une stratégie pour gérer les manquants.
Découvrons quelques techniques qui permettront d’obtenir de meilleurs résultats d’analyse.

Repérage des données manquantes


Commençons par un exemple qui permet de repérer facilement les valeurs manquantes en
faisant un test binaire. Nous affichons la valeur True (vrai) lorsque la donnée est manquante.

import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, np.NaN, 5, 6, None])

print(s.isnull())

print()
print(s[s.isnull()])

Plusieurs conventions existent pour symboliser les données manquantes dans un jeu. Dans
notre exemple, nous gérons le marquage par np.NaN (NumPy NaN) et par la valeur prédéfinie
de Python None.
La recherche des manquants utilise la méthode isnull(). En ajoutant un index au jeu de
données, nous pouvons n’afficher que les entrées manquantes. Voici le résultat des opérations
d’affichage :

False
1 False
2 False
3 True
4 False
5 False
6 True
dtype: bool

3 NaN
6 NaN
dtype: float64

Encodage des manquants


Une fois que vous avez constaté la présence de données manquantes, il faut décider quoi en
faire. Vous pouvez soit ignorer la situation, soit remplir les trous, soit supprimer les entrées
correspondantes du jeu. La première approche risque de vous causer bien des soucis
d’analyse, et elle est rarement utilisée. La technique que nous proposons maintenant permet
de remplir les données manquantes ou de supprimer les entrées manquantes :
import pandas as pd
import numpy as np

s = pd.Series([1, 2, 3, np.NaN, 5, 6, None])

print(s.fillna(int(s.mean())))
print()
print(s.dropna())

Nous exploitons deux méthodes ici : fillna() remplit les trous et dropna() qui supprime
l’entrée manquante. La première méthode oblige à fournir une valeur à insérer dans le trou.
Dans l’exemple, nous partons de la moyenne de toutes les valeurs, mais autre chose est bien
sûr possible. Voici le résultat d’exécution de l’exemple :

0 1.0
1 2.0
2 3.0
3 3.0
4 5.0
5 6.0
6 3.0
dtype: float64

0 1.0
1 2.0
2 3.0
4 5.0
5 6.0
dtype: float64

Lorsque le jeu de données ne comporte qu’une série, les choses restent simples. En revanche,
dès que vous travaillez avec un datagramme, elles se compliquent. Vous pouvez toujours
choisir de supprimer la totalité de la ligne. De même, face à une colonne contenant de
nombreux manquants, vous pouvez la supprimer. Si vous voulez remplir les données
manquantes, les choses sont plus complexes, car il faut considérer le jeu de données de façon
globale, en plus des besoins au niveau de chaque champ individuel.

Imputation des données manquantes


Les techniques de la section précédente permettent de se faire une idée de ce qu’est
l’opération d’imputation de données manquantes. La technique à adopter dans chaque cas
dépend du genre de données à traiter. Si vous travaillez à partir d’une structure arborescente,
vous pouvez par exemple remplacer les valeurs manquantes par la valeur -1. Vous laissez dans
ce cas l’algorithme d’imputation remplir les valeurs manquantes afin d’aboutir aux meilleures
valeurs possibles pour les manquants. Nous reviendrons sur les arborescences dans les
Chapitres 15 et 20. L’exemple suivant donne un premier exemple d’imputation de deux valeurs
de données manquantes :

import pandas as pd
import numpy as np
from sklearn.preprocessing import Imputer

s = [[1, 2, 3, np.NaN, 5, 6, None]]

imp = Imputer(missing_values=’NaN’,
strategy=’mean’, axis=0)

imp.fit([[1, 2, 3, 4, 5, 6, 7]])

x = pd.Series(imp.transform(s).tolist()[0])

print(x)

La série s comporte deux trous. Nous créons un objet du type Imputer qui va insérer d’autres
valeurs à leur place. Le paramètre nommé missing_values permet d’indiquer le symbole que
nous recherchons, NaN. Le paramètre axis est ici égal à zéro, ce qui fait progresser
l’imputation par colonnes (la valeur 1 la fait progresser par lignes). Enfin, le paramètre
strategy décide de la façon dont il faut remplacer les manquants.
» mean : remplace les valeurs manquantes par la moyenne selon le même axe.
» median : remplace les valeurs par la médiane le long du même axe.
» most_frequent : remplace les manquants par la valeur la plus fréquente selon le même
axe.

Avant de réaliser une imputation, vous devez alimenter l’objet imputeur avec des statistiques
au moyen d’un appel à fit(). Dans l’exemple, nous appelons ensuite transform() en
l’appliquant à s pour effectuer le remplissage et affichons le résultat sous forme d’une série.
Pour créer cette série, il nous faut convertir la sortie de l’imputeur vers le format liste qui va
devenir l’entrée de Series(). Voici le résultat final, une fois les valeurs remplacées :

0 1.0
1 2.0
2 3.0
3 4.0
4 5.0
5 6.0
6 7.0
dtype: float64

Tranchage et débitage : filtrer et sélectionner des


données
Vous n’aurez pas toujours besoin d’exploiter la totalité du jeu de données d’entrée. Vous
pouvez par exemple avoir suffisamment d’informations en ne retenant que la colonne des âges,
ou seulement les lignes qui contiennent une quantité d’informations suffisante. Deux étapes
sont à prévoir pour réduire le volume de données à préparer pour un traitement :
1. Filtrage des lignes pour obtenir un sous-ensemble répondant à un certain
critère, par exemple tous les individus dont l’âge est entre 5 et 10 ans.
2. élection des seules colonnes contenant les données nécessaires. Vous n’avez par
exemple pas besoin des noms de famille des individus sauf dans les rares analyses qui
ciblent les patronymes.
Ces deux opérations correspondent au tranchage (slicing) et au débitage (dicing). Découvrons
les techniques permettant de réaliser ces opérations de sélection pour adapter les données à
des besoins.

Tranchage de lignes
L’opération de tranchage peut revêtir plusieurs formes ; celle qui nous intéresse ici consiste à
enlever des données depuis des lignes en deux ou en trois dimensions. Un tableau à deux
dimensions (lignes et colonnes) contiendra par exemple des températures sur l’axe horizontal
et le temps sur l’axe vertical. Sélectionner par tranchage revient à conserver les températures
à un moment donné. Vous pouvez parfois combiner les lignes à des cas.
Pour un tableau à trois dimensions, nous envisageons par exemple un axe pour les adresses
(celui des x), un axe pour les produits (celui des y) et un axe pour le temps (celui des z). Cela
permet de voir l’évolution des ventes de produits au cours du temps. Pour savoir si les ventes
d’un produit en particulier sont en augmentation, et où, il s’agit de sélectionner une ligne pour
les ventes d’un produit pour chaque lieu de vente. Voyons comment obtenir cette sélection.

x = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9],],


[[11,12,13], [14,15,16], [17,18,19],],
[[21,22,23], [24,25,26], [27,28,29]]])
x[1]

Dans l’exemple, nous commençons par construire un tableau à trois dimensions, puis nous
extrayons la seule ligne 1 (la deuxième) pour obtenir ceci :

array([[11, 12, 13],


[14, 15, 16],
[17, 18, 19]])

Sélection de colonnes
Sélectionner les colonnes revient à appliquer une rotation d’un angle droit par rapport aux
lignes de la sélection précédente. Dans le cas d’un tableau à deux dimensions, cela correspond
par exemple à la sélection des moments correspondant à certaines températures. Pour
l’exemple commercial, cela permet d’obtenir les ventes de tous les produits à un certain
endroit et à un certain moment. Vous pourrez parfois associer des colonnes à des
caractéristiques du jeu de données. L’exemple suivant montre comment réaliser cette sélection
en partant du même tableau d’entrée que le précédent :

x = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9],],


[[11,12,13], [14,15,16], [17,18,19],],
[[21,22,23], [24,25,26], [27,28,29]]])
x[:,1]

Il y a dorénavant deux niveaux d’indexation : le premier index correspond à la ligne. Le signe


deux-points demande d’utiliser toutes les lignes. Le second niveau d’index correspond à la
colonne. Dans notre exemple, nous demandons la deuxième colonne (colonne 1). Voici le
résultat :

array([[ 4, 5, 6],
[14, 15, 16],
[24, 25, 26]])

Rappelons qu’il s’agit ici d’un tableau à trois dimensions. Chacune des colonnes contient tous
les éléments de l’axe z. Le résultat montre toutes les lignes (de 0 à 2) pour la colonne 1 (la
deuxième) avec tous les éléments de l’axe z de 0 à 2 pour cette colonne.

Débitage (dicing)
L’opération de débitage suppose de trancher dans les deux directions pour obtenir un sous-
bloc. Dans le cas d’un tableau en 3D, vous pouvez ainsi récupérer les ventes d’un seul produit à
un seul endroit, mais à tout moment. L’exemple suivant reprend le même tableau que les deux
précédents exemples :

x = np.array([[[1, 2, 3], [4, 5, 6], [7, 8, 9],],


[[11,12,13], [14,15,16], [17,18,19],],
[[21,22,23], [24,25,26], [27,28,29]]])
print(x[1,1])
print(x[:,1,1])
print(x[1,:,1])
print()
print(x[1:3, 1:3])

Nous réalisons quatre débitages différents. Dans le premier, nous récupérons la ligne 1,
colonne 1, puis la colonne 1, axe z 1. Dans le troisième débitage, nous récupérons la
colonne 1 pour l’axe z 1. Enfin, nous demandons les lignes 1 et 2 pour les colonnes 1 et 2. Voici
le résultat des quatre opérations :

[14 15 16]
[ 5 15 25]
[12 15 18]

[[[14 15 16]]]
[17 18 19]]

[[24 25 26]
[27 28 29]]]

Concaténation et transformation
Les données d’entrée se présentent très rarement prêtes à l’emploi. Vous aurez souvent besoin
de puiser dans plusieurs bases de données, chacune imposant son format spécifique. Il est
impossible de réaliser des analyses pertinentes à partir de sources divergentes. Vous devez
donc créer un seul jeu de données unifié pour obtenir des données exploitables, ce qui suppose
de combiner les données, opération appelée concaténation.
Il s’agit d’abord de s’assurer que tous les champs portant les mêmes données dans le jeu
destinataire ont les mêmes caractéristiques. Par exemple, un champ contenant un âge peut
être stocké en tant que chaîne dans une première base, mais en tant que valeur numérique
entière dans une autre. Pour que les champs puissent être valablement exploités, ils doivent
tous être du même type.
Découvrons les opérations à prévoir pour concaténer et transformer les données de plusieurs
sources afin d’obtenir un seul jeu pertinent pour une analyse. L’objectif est d’obtenir le jeu
unifié qui représente correctement les données des différents jeux hétérogènes. Mais ces
données ne doivent pas être altérées, au risque de produire des résultats faussés.

Ajout de nouveaux cas et de nouvelles variables


Vous aurez souvent besoin de combiner des jeux de données, voire d’y ajouter des informations
pour améliorer l’analyse. Vous obtenez ainsi un jeu combiné qui peut contenir de nouveaux cas
ou de nouvelles variables. L’exemple suivant réalise ces deux opérations :

import pandas as pd

df = pd.DataFrame({‘A’: [2,3,1],
‘B’: [1,2,3],
‘C’: [5,3,4]})

df1 = pd.DataFrame({‘A’: [4],


‘B’: [4],
‘C’: [4]})

df = df.append(df1)
df = df.reset_index(drop=True)
print(df)

df.loc[df.last_valid_index() + 1] = [5, 5, 5]
print()
print(df)

df2 = pd.DataFrame({‘D’: [1, 2, 3, 4, 5]})

df = pd.DataFrame.join(df, df2)
print()
print(df)

Pour ajouter des données à un cadre DataFrame existant, le plus simple consiste à exploiter la
méthode nommée append(). (Nous verrons comment utiliser la méthode concat() décrite
dans le Chapitre 13.) Dans notre exemple, nous avons trois cas dans df qui sont combinés vers
un seul cas dans df1. Il est indispensable que les colonnes de ces deux jeux correspondent
pour que les données soient ajoutées correctement. En effet, lorsque vous ajoutez deux objets
DataFrame l’un à l’autre, le nouvel objet contient les valeurs de l’ancien index. Pour simplifier
l’accès aux différents cas, vous générez un nouvel index au moyen de reset_index().
Pour ajouter un nouveau cas à un jeu DataFrame existant, vous pouvez le créer directement.
Cela se produit dès que vous insérez une nouvelle entrée localisée à une position supérieure
de 1 au dernier index valide par last_valid_index().
Lorsque vous avez besoin d’ajouter une colonne (une nouvelle variable) à l’objet DataFrame,
vous utilisez join(). L’objet résultant met en correspondance les cas ayant la même valeur
d’index. C’est pourquoi l’indexation est essentielle. Enfin, sauf si vous tolérez des valeurs
vides, le nombre de cas des deux objets DataFrame doit être identique. Voici les trois
affichages résultant de cet exemple :

A B C
0 2 1 5
1 3 2 3
2 1 3 4
3 4 4 4

A B C
0 2 1 5
1 3 2 3
2 1 3 4
3 4 4 4
4 5 5 5

A B C D
0 2 1 5 1
1 3 2 3 2
2 1 3 4 3
3 4 4 4 4
4 5 5 5 5

Suppression de données
Lorsque vous avez besoin de supprimer des cas (lignes) ou des variables (colonnes) d’un jeu
car vous n’en avez pas besoin dans l’analyse, vous utilisez la méthode drop(). L’opération va
supprimer soit des cas, soit des variables, en fonction de la façon dont vous fournissez les
paramètres à la méthode, comme le montre l’exemple :

import pandas as pd

df = pd.DataFrame({‘A’: [1,2,3],
‘B’: [4,5,6],
‘C’: [7,8,9]})

df = df.drop(df.index[[1]])
print(df)

df = df.drop(‘B’, 1)
print()
print(df)

Dans l’exemple, nous supprimons d’abord le deuxième cas de df (cette ligne correspond
visuellement à la deuxième colonne dans la définition ci-dessus). Vous constatez qu’il faut
fournir un index. Vous pouvez supprimer un seul cas, comme dans l’exemple, ou bien une plage
de cas en les séparant par des virgules. Vous devez bien sûr être très vigilant au niveau des
numéros des index.
Supprimer une colonne se fait différemment. Vous fournissez son nom ou son index. Dans les
deux cas, il faut fournir un axe qui est normalement 1. Voici le résultat des deux suppressions
de cet exemple :

A B C
0 1 4 7
2 3 6 9
A C
0 1 7
2 3 9

Trier et mélanger
Trier et mélanger sont les deux opérations complémentaires pour maîtriser l’ordre d’apparition
des données. La première opération place les données dans un certain ordre alors que la
seconde supprime tout ordre remarquable. Il faut savoir qu’en général, il n’est pas conseillé de
trier les données d’entrée d’une analyse car cela peut influer sur la pertinence des résultats.
En revanche, vous aurez souvent besoin de trier les données par la suite pour les présenter.
L’exemple suivant effectue un tri puis un mélange :

import pandas as pd
import numpy as np

df = pd.DataFrame({‘A’: [2,1,2,3,3,5,4],
‘B’: [1,2,3,5,4,2,5],
‘C’: [5,3,4,1,1,2,3]})

df = df.sort_values(by=[‘A’, ‘B’], ascending=[True, True])


df = df.reset_index(drop=True)
print(df)

index = df.index.tolist()
np.random.shuffle(index)
df = df.loc[df.index[index]]
df = df.reset_index(drop=True)
print()
print(df)

La lecture de l’exemple montre que le tri est beaucoup plus simple que le mélange. En effet,
pour trier les données, vous vous servez de la méthode sort_ values() en indiquant la
colonne servant de clé de tri. Vous pouvez choisir de trier en ordre ascendant ou descendant. Il
ne faut pas oublier d’appeler ensuite reset_index() pour que les index se présentent dans
l’ordre correct pour les traitements ultérieurs.
Pour effectuer un mélange, il faut d’abord récupérer l’index actuel avec df.index.tolist() et
le stocker dans index. Vous mélangez l’index par appel à random_shuffle(). Il ne reste plus
qu’à appliquer ce nouvel ordre à df au moyen de loc[]. N’oubliez pas enfin de générer le
nouvel index avec reset_index(). Voici le résultat de ces deux opérations, en sachant que
l’affichage de la seconde ne correspondra sans doute pas au même ordre que lors de votre
exécution :

A B C
0 1 2 3
1 2 1 5
2 2 3 4
3 3 4 1
4 3 5 1
5 4 5 3
6 5 2 2
A B C
0 2 1 5
1 4 5 3
2 1 2 3
3 3 4 1
4 2 3 4
5 3 5 1
6 5 2 2

Agrégation des données par niveau


L’opération d’agrégation consiste à combiner ou regrouper des données dans un ensemble, un
sac ou une liste, même si les données ne sont pas apparentées. En général, l’agrégation va
chercher à regrouper plusieurs lignes en appliquant un algorithme statistique, par exemple
une moyenne, un comptage, un maximum, un minimum, une médiane, un mode ou une somme.
L’agrégation de données offre plusieurs avantages :
» Les analyses sont simplifiées.
» Il devient plus difficile de reconstruire les données d’un individu en particulier à partir du
jeu (préservation de la confidentialité).
» L’agrégation permet de combiner un élément donné d’une source en correspondance
avec un élément combiné d’une autre source.

La principale raison d’être de l’opération d’agrégation des données est d’ajouter de l’anonymat
pour des raisons juridiques ou autres. En effet, même lorsque les données doivent rester
anonymes, elles peuvent permettre d’identifier un individu en employant les bonnes techniques
d’analyse. C’est ainsi que des chercheurs ont pu prouver que l’on pouvait identifier une
personne à partir de trois achats par carte bancaire
(https://www.nature.com/articles/srep01376). L’exemple suivant réalise trois tâches
d’agrégation :
import pandas as pd

df = pd.DataFrame({‘Mappe’: [0,0,0,1,1,2,2],
‘Valeurs’: [1,2,3,5,4,2,5]})

df[‘S’] = df.groupby(‘Mappe’)[‘Valeurs’].transform(np.sum)
df[‘M’] = df.groupby(‘Mappe’)[‘Valeurs’].transform(np.mean)
df[‘V’] = df.groupby(‘Mappe’)[‘Valeurs’].transform(np.var)

print(df)

Nous partons de deux caractéristiques pour cet objet df. Les valeurs dans Mappe définissent
quels éléments dans Valeurs vont ensemble. Pour calculer la somme des éléments de Mappe à
index 0, nous utilisons les éléments de Valeurs, 2 et 3.
Il faut d’abord utiliser groupby() pour regrouper les valeurs de Mappe, puis les indexer dans
Valeurs et appeler transform() pour générer les données agrégées en ayant recours à un
des algorithmes de la librairie NumPy, par exemple np.sum. Voici les résultats de cet exemple :

Mappe Valeurs S M V
0 0 1 6 2.0 1.0
1 0 2 6 2.0 1.0
2 0 3 6 2.0 1.0
3 1 5 9 4.5 0.5
4 1 4 9 4.5 0.5
5 2 2 7 3.5 4.5
6 2 5 7 3.5 4.5
Chapitre 8
Épuration des données
DANS CE CHAPITRE :
» Traitement des données HTML et XML

» Traitement des textes bruts

» Le concept de sac de mots et autres techniques

» Traitement de données graphiques

D ans le Chapitre 6,
nous avons appris à charger différents types de fichiers et dans le Chapitre 7,
nous avons découvert plusieurs techniques pour préparer les données, et notamment les tris et les
sélections. Mais ces opérations de chargement amélioré ne suffisent pas. Il s’agit ensuite d’épurer
les données pour ne conserver que ce qui mérite de subir l’analyse, et c’est l’objectif de ce chapitre.
Nous allons traiter plusieurs types de fichiers, afin d’en récupérer les contenus significatifs, dans un
grand nombre de formats comme le HTML, le XML, le texte brut et d’autres. Nous verrons même
comment intervenir sur un fichier d’image graphique.
Dans la suite du livre, vous allez découvrir que les données peuvent être remodelées de très
nombreuses façons. Pour la machine, ce ne sont que des zéros et des un ; ce sont les humains qui
donnent du sens aux données en intervenant sur leur format, leur stockage et bien sûr leur
interprétation. Une série de zéros et de un peut correspondre à un salaire, à une date ou une lettre
d’amour, tout dépendant de cette interprétation. Les structures contenant les données fournissent
quelques pistes quant à la façon dont elles peuvent être interprétées. Dans ce chapitre, nous allons
voir pourquoi tout datalogue qui utilise Python doit chercher à trouver des motifs remarquables dans
les données. Ceci ainsi qu’un traitement d’épuration permet de découvrir des informations là où vous
ne pensez même pas qu’il y ait quoi que ce soit de remarquable.
Le fichier d’exemple de ce chapitre correspond à PYDASC_08.

Traitements HTML et XML


Les formats HTML et XML présentent les données sous une forme d’arborescence hiérarchique. Dans
la variante HTML qui structure toutes les pages sur le Web, vous pouvez rencontrer quelques soucis
parce que les règles de balisage sont suivies de manière plus ou moins stricte, alors que le format
XML est tout à fait implacable, car toujours en conformité avec le standard. Cela dit, les techniques
employées pour analyser ces formats sont les mêmes. Voyons donc d’abord quelle technique générale
s’applique au format HTML.
Il vous arrivera de ne pas avoir besoin de toutes les données que contient une page, mais seulement
une sélection, et vous tirerez profit dans ce cas du mécanisme XPath qui permet de repérer et de
sélectionner certaines données dans une page HTML, puis de les extraire à votre profit.

Analyse XML et HTML


Il ne suffit pas en général de charger les données depuis un fichier, par exemple XML, vers une
variable du programme, comme nous l’avons vu dans le Chapitre 6. En effet, dans ce Chapitre 6, nous
avons placé les données dans un objet conteneur de type DataFrame, qui comportait trois colonnes
ayant le type de données chaîne (str). Mais de nombreuses opérations sur les données ne sont pas
possibles avec ce type de chaîne de caractères. Voyons donc un exemple qui exploite les données XML
déjà vues dans le Chapitre 6 pour aboutir à un nouvel objet DataFrame contenant uniquement les
éléments <Number> et <Boolean> dans un format approprié.

from lxml import objectify


import pandas as pd
from distutils import util

xml = objectify.parse(open(‘XMLData.xml’))
root = xml.getroot()
df = pd.DataFrame(columns=(‘Number’, ‘Boolean’))

for i in range(0, 4):


obj = root.getchildren()[i].getchildren()
row = dict(zip([‘Number’, ‘Boolean’],
[obj[0].pyval,
bool(util.strtobool(obj[2].text))]))

row_s = pd.Series(row)
row_s.name = obj[1].text
df = df.append(row_s)

print(type(df.loc[‘Primo’][‘Number’]))
print(type(df.loc[‘Primo’][‘Boolean’]))

L’objet df de type DataFrame est créé vide, puis rempli dans la boucle de répétition for qui traverse
les nœuds enfants de la racine de la structure. La liste produite contient les éléments suivants :
» un élément <Number> (int) ;
» un élément ordinal qui est une chaîne (string) ;
» un élément <Boolean> de type chaîne aussi.

Le booléen sert à incrémenter l’objet df. Le code se base sur l’élément ordinal comme label d’index et
construit chaque nouvelle ligne à insérer dans l’objet df. L’idée est de convertir les données trouvées
dans l’arborescence XML vers le bon type de données à stocker dans df. Les éléments numériques
sont directement disponibles comme valeurs numériques entières int. En revanche, pour l’élément
booléen : il faut convertir la chaîne en une valeur numérique avec la fonction strtobool() de
distutils.util. On obtient ainsi la valeur numérique 0 pour la valeur booléenne False et la
valeur 1 pour la valeur True. Ce ne sont pas encore des valeurs du type booléen, mais des valeurs
numériques entières. Il reste donc à convertir ces chiffres 0 et 1 au moyen de bool().
L’exemple montre en outre comment accéder aux valeurs individuelles dans l’objet DataFrame. La
propriété name se sert de l’élément de type chaîne <String> pour se simplifier l’accès. Vous lui
fournissez un index avec loc[] puis accédez à la caractéristique désirée grâce à un second index.
Voici le résultat qu’affiche cet exemple :

<class ‘int’>
<class ‘bool’>

Extraction de données avec XPath


Le mécanisme XPath peut grandement simplifier l’extraction des données d’un jeu, et améliorer les
performances. L’exemple suivant montre une reformulation avec XPath de l’exemple précédent. Le
code est un peu plus bref. Il n’utilise pas de boucle de répétition for.

from lxml import objectify


import pandas as pd
from distutils import util
xml = objectify.parse(open(‘XMLData.xml’))
root = xml.getroot()

map_number = map(int, root.xpath(‘Record/Number’))


map_bool = map(str, root.xpath(‘Record/Boolean’))
map_bool = map(util.strtobool, map_bool)
map_bool = map(bool, map_bool)
map_string = map(str, root.xpath(‘Record/String’))

data = list(zip(map_number, map_bool))

df = pd.DataFrame(data,
columns=(‘Number’, ‘Boolean’),
index = list(map_string))

print(df)
print(type(df.loc[‘Primo’][‘Number’]))
print(type(df.loc[‘Primo’][‘Boolean’]))

La partie préparatoire ne change pas par rapport à la version précédente : importation des données et
obtention du nœud racine. L’exemple crée ensuite un objet de données dans lequel sont placées des
paires regroupant numéro d’enregistrement et valeur booléenne. Dans le fichier XML, les données
sont toutes de type chaîne de caractères. Nous utilisons donc la fonction map() pour convertir les
chaînes correctement. Pour le numéro d’enregistrement, il suffit de convertir vers le type entier int.
La fonction xpath() reçoit en paramètre un chemin depuis la racine jusqu’aux données désirées, ici,
‘Record/Number’.
Pour la valeur booléenne, les choses sont un peu plus ardues. Nous utilisons toujours la fonction
strtobool() pour convertir la valeur chaîne vers un numérique qui est ensuite converti en booléen
par bool(). Vous pouvez cependant éviter la double conversion, au risque de recevoir une erreur
prétendant que les listes ne possèdent pas la fonction tolower(). Nous contournons ce souci en
effectuant une triple conversion, c’est-à-dire en effectuant d’abord une conversion vers chaîne au
moyen de la fonction str().
L’objet de données DataFrame n’est plus créé de la même façon. Toutes les lignes sont insérées en
même temps au moyen de data. Les noms des colonnes sont obtenus comme dans l’exemple
précédent, mais il nous faut maintenant pouvoir ajouter les noms des lignes. Nous utilisons le
paramètre index appliqué à une version associée (mappée) de la sortie de xpath(), pour le chemin
menant à ‘Record/String’. Voici l’affichage résultant :

Number Boolean
Primo 1 True
Secundo 2 False
Tercio 3 True
Quarto 4 False
<class ‘numpy.int64’>
<class ‘numpy.bool_’>

Épuration de fichiers texte bruts


Par nature, un fichier de texte brut ne contient aucun caractère de formatage, et devrait donc être
facile à analyser. Il faut cependant étudier la façon dont le contenu est stocké, et voir s’il n’y a pas des
mots ayant un sens particulier. Par ailleurs, il existe plusieurs variantes du codage de caractères
Unicode ; vous devez en tenir compte pour ne pas rencontrer d’erreur d’interprétation. Enfin, vous
pouvez utiliser des masques de sélection sous forme d’expressions régulières (regex) pour repérer des
informations spécifiques dans un tel fichier. Ces expressions permettent de faire un nettoyage des
données et de rechercher des motifs. Découvrons les différentes techniques applicables à un fichier
de texte brut.

Gestion d’Unicode
Un fichier texte ne contient par définition que du texte, mais les valeurs numériques qui
correspondent aux différentes lettres et chiffres de ce texte varient en fonction du codage utilisé. On
peut en effet coder un caractère alphabétique sur 7 bits, 8 bits, ou, de plus en plus souvent, sur
plusieurs octets. De même, les caractères non américains sont souvent codés de façon variable. Il est
donc indispensable de connaître la norme utilisée pour coder les signes. Plusieurs encodages sont
visibles dans la page http://www.i18nguy.com/unicode/code-pages.html.
La lignée 3.x de Python a résolument opté pour le standard de codage UTF-8 aussi bien pour la
lecture que pour l’écriture des fichiers. Pour exploiter un autre encodage, par exemple celui nommé
ASCII, vous devrez utiliser une solution de contournement. Dans le cas contraire, vous obtiendrez une
erreur. Un article en anglais donne quelques détails au sujet de ce contournement éventuel
(https://docs.python.org/3/howto/unicode.html).
Si vous tentez de traiter un fichier qui n’est pas généré avec le bon encodage, vous ne pourrez
évidemment pas réaliser vos analyses, ni importer des modules. Vous devez donc bien tester votre
code et vérifier dès le départ qu’il n’y a pas un problème de codage qui empêcherait votre projet de
s’exécuter. Vous trouverez des informations supplémentaires à ce sujet dans les deux pages
suivantes :
» http://blog.notdot.net/2010/07/Getting-unicode-right-in-Python
» http://web.archive.org/web/20120722170929/http://boodebr.org/main/python/all-
about-python-and-unicode

Racinisation et suppression des mots vides


Malgré son nom étrange, la racinisation est une opération simple (stemming) : elle consiste à
regrouper les variantes d’un mot derrière le radical ou la racine du mot. Ne confondez pas cette
opération avec l’étymologie qui cherche par exemple la racine latine d’un mot français. Ici, le but est
de simplifier en éliminant les variantes d’un mot qui n’apportent quasi rien de plus au niveau
sémantique. C’est ainsi que vous pouvez raciniser vers le mot chat les mots chatte, chaton et
chatière. La racinisation vous aide dans l’analyse des phrases en produisant un inventaire des mots
plus efficace ; en effet, l’algorithme d’apprentissage n’a besoin de gérer dans ce cas que le mot
racine, et non les différentes variantes.
L’élimination des déclinaisons et suffixes pour n’obtenir que des mots racines, et plus généralement la
création de deux listes de mots à partir de phrases, ne constitue qu’une partie du processus complet
de création d’un programme exploitant un langage humain. En effet, les langages humains utilisent
un grand nombre de mots de liaison qui ont beaucoup de sens pour les humains, mais peu pour une
machine. C’est le cas des articles et de nombreux adverbes. Ces mots sont appelés mots vides (stop
words), en opposition aux mots pleins que vous voulez conserver. Si vous supprimez ces mots vides
dans une phrase, elle devient incompréhensible pour un humain, mais pour un ordinateur, ces mots
vides constituent un bruit de fond qui peut même empêcher l’analyse des phrases, d’où le terme
anglais stop word.
La racinisation et la suppression des mots vides vont donc alléger le texte en diminuant le nombre
d’éléments pour garder l’essentiel. Vous n’obtenez plus que les termes qui ont un grand impact sur la
signification de chaque phrase. Des phrases ainsi allégées permettent aux algorithmes de travailler
plus efficacement.
Notre prochain exemple requiert la présence d’une extension NLTK (Natural Language ToolKit) qui
n’est pas installée par défaut avec Anaconda (nous supposons qu’Anaconda a été installé comme
décrit dans le Chapitre 3). Vous devez télécharger puis installer NLTK en vous inspirant des
instructions données dans la page http://www.nltk.org/install.html, en choisissant bien votre
système. Sélectionnez la version de NLTK compatible avec votre version de Python (si vous avez
installé plusieurs versions sur votre machine). Vous devez aussi installer des paquetages dépendants.
La page http://www.nltk.org/data.html montre comment procéder. Installez bien tous les
paquetages.
N. d. T. : Nous fournissons dans le fichier archive des exemples un petit fichier d’aide pour mettre en
place NLTK.

Voyons donc comment réaliser une racinisation et une purge des mots vides dans une phrase. Dans la
première phase, nous apprenons un à un à l’algorithme comment analyser à partir d’une phrase test.
Dans une seconde phase, nous traitons une autre phrase en cherchant les mots qui se trouvent dans
la première.

from sklearn.feature_extraction.text import *


from nltk import word_tokenize
from nltk.stem.porter import PorterStemmer

stemmer = PorterStemmer()

def stem_tokens(tokens, stemmer):


stemmed = []
for item in tokens:
stemmed.append(stemmer.stem(item))
return stemmed

def tokenize(text):
tokens = word_tokenize(text)
stems = stem_tokens(tokens, stemmer)
return stems

vocab = [‘Sam loves swimming so he swims all the time’]


vect = CountVectorizer(tokenizer=tokenize,
stop_words=’english’)
vec = vect.fit(vocab)

sentence1 = vec.transform([‘George loves swimming too!’])

print(vec.get_feature_names())
print(sentence1.toarray())

L’exemple crée un vocabulaire (une liste de mots) à partir d’une phrase de test, et stocke celui-ci dans
la variable vocab. Nous créons ensuite un objet de type CountVectorizer nommé vect qui va
recevoir la liste des mots pleins, une fois les mots vides éliminés. Le paramètre tokenizer indique
quelle fonction utiliser pour la racinisation. Le paramètre stop_words désigne un fichier texte
contenant la liste des mots vides du langage concerné. Dans notre cas, c’est la langue anglaise. Il
existe des fichiers de mots vides pour un certain nombre d’autres langues, et notamment le français
et l’allemand. Tous les autres paramètres de CountVectorizer sont décrits dans la page
https://scikit-
learn.org/stable/modules/generated/sklearn.feature_extraction.text.CountVectorizer.html.
Le vocabulaire est ensuite inséré dans une autre variable CountVectorizer nommée vec avec
laquelle nous réalisons le traitement de la phrase de test au moyen de la fonction transform(). Voici
le résultat affiché par cet exemple :

[‘love’, ‘sam’, ‘swim’, ‘time’]


[[1 0 1 0]]
La première ligne indique les mots pleins trouvés. Vous remarquez que les déclinaisons
n’apparaissent plus. Tous les mots vides ont disparu.
La seconde ligne d’affichage indique le nombre d’occurrences de chaque mot plein dans la phrase de
test. Dans l’exemple, le premier mot apparaît une fois ainsi que le troisième. Les deux autres mots
n’ont pas été trouvés dans la seconde phrase, ce qui donne bien la valeur 0.
N. d. T. : Le fichier d’exemple français contient une variante de cet exercice avec une liste française
des mots vides. Le résultat n’est pas parfait, mais il suffit à montrer le principe.

Expressions régulières
Les expressions régulières offrent au datalogue un intéressant ensemble d’outils pour analyser du
texte brut. Lors de vos premiers pas, vous pourriez être effrayé par l’apparence de ces expressions et
leur fonctionnement exact. Pour vous aider, vous pouvez vous servir d’un site tel que
https://regexr.com/ pour tester des expressions régulières afin de voir comment formuler celle
dont vous avez besoin. Le principe consiste à considérer certains caractères et symboles comme ayant
une valeur spéciale (contexte dans lequel on parle de métacaractères). Le grand Tableau 8.1 qui suit
donne une liste d’expressions régulières avec une description de l’effet de chacune. Dans la première
colonne, la mention xr signifie expression régulière.

Tableau 8.1 : Métacaractères des expressions régulières Python.

Caractère Description
(xr) Regroupe des expressions régulières et mémorise le texte trouvé
(? : xr) Regroupe des expressions régulières sans mémoriser le texte trouvé
(?#...) Commentaire non pris en compte
xr? Trouve 0 ou 1 occurrence de l’expression (mais pas plus)
xr* Trouve 0 ou plus occurrences de l’expression
xr+ Trouve 1 ou plus occurrences de l’expression
(?> xr) Trouve un motif indépendant sans backtracking
. Trouve un caractère à part Newline (\n) (sauf si ajout de l’option m)
[^...] Trouve un caractère ou une plage de caractères absents du masque
[...] Trouve un caractère ou une plage de caractères présents dans le masque
xr{ n, m} Trouve entre n et m occurrences de l’expression
\n, \t, etc. Trouve les caractères de contrôle comme Newline (\n), Retour Chariot (\r) et Tab (\t)
\d Trouve les chiffres (équivalent à [0-9])
a|b Trouve soit a, soit b
xr{ n} Trouve le nombre exact n d’occurrences
xr{ n,} Trouve n ou plus occurrences
\D Trouve les non-chiffres
\S Trouve les non-espaces
\B Trouve en dehors des débuts et/ou des fins des mots
\W Trouve les caractères hors des mots
\1...\9 Trouve la sous-expression de rang n
\10 Trouve la sous-expression de rang n si elle a déjà réussi
\A Trouve le début d’une chaîne
^ Trouve le début d’une ligne
\z Trouve la fin d’une chaîne
\Z Trouve la fin d’une chaîne (si saut de ligne, trouve juste avant)
$ Trouve la fin d’une ligne
\G Trouve le point de fin du dernier motif trouvé
\s Trouve les espaces (équivaut à [\t\n\r\f])
\b Trouve les extrémités des mots si hors des crochets et trouve Backspace (0x08) dans les crochets
\w Trouve des caractères de mots
(?= xr) Stipule une position avec un motif (sans plage)
(? ! xr) Stipule la négation d’une position avec un motif (sans plage)
(?-imx) Désactive temporairement l’état des options i, m ou x dans une xr (si entre parenthèses,
seulement dans la zone entre parenthèses)
(?imx) Active temporairement l’état des options i, m ou x dans une xr (si entre parenthèses, seulement
dans la zone entre parenthèses)
(?-imx: xr) Désactive temporairement l’état des options i, m ou x dans la zone entre parenthèses
(?imx: xr) Active temporairement l’état des options i, m ou x dans la zone entre parenthèses

Avec les expressions régulières, vous allez pouvoir effectuer des traitements préalables, avant ceux
présentés dans la suite du chapitre. Voyons par exemple comment extraire les numéros de téléphone
de deux phrases dans lesquelles ils sont présentés de façon différente. Ce genre de capture de
données vous sera très utile dès que vous devrez prendre en charge des fichiers qui ne sont pas
formatés de la même façon. En étudiant le masque défini dans cet exemple, vous aurez une bonne
idée du principe des expressions régulières, et pourrez l’appliquer à toutes sortes de projets.

import re

data1 = ‘Mon numéro est le 800-555-1212.’


data2 = ‘800-555-1234 est mon téléphone perso.’

pattern = re.compile(r’(\d{3})-(\d{3})-(\d{4})’)

dmatch1 = pattern.search(data1).groups()
dmatch2 = pattern.search(data2).groups()

print(dmatch1)
print(dmatch2)

Dans nos deux phrases de test, le nouveau téléphone n’apparaît pas au même endroit. Notre masque
doit se lire de gauche à droite. Dès qu’il est défini, nous cherchons cinq groupes de deux chiffres
séparés par un tiret.
Pour que la recherche soit plus rapide, nous appelons la fonction compile() qui va produire une
version compilée du masque. Cela évite à Python de le réinterpréter à chaque utilisation. Le résultat
de cette compilation est stocké dans pattern.
La fonction search() applique le masque à chacune des deux phrases, puis stocke ce qu’elle a trouvé
dans des groupes puis produit une structure de type tuple dans deux variables. Voici ce qui est
affiché :

(‘800’, ‘555’, ‘1212’)


(‘800’, ‘555’, ‘1234’)

Sacs de mots et analyse lexicale


En datalogie, si vous importez des données, c’est pour les analyser. Mais avant d’analyser des
données de type texte, il y a lieu de distribuer le contenu en autant de mots isolés, opération dite
d’analyse lexicale (tokenization). Le résultat de cette opération est un sac de mots (bag of words,
souvent abrégé en BOW). Ce sac vous sert ensuite à entraîner des classificateurs qui sont des
algorithmes pour distribuer les mots en plusieurs catégories. Voyons comment utiliser cette technique
du sac de mots.

LE JEU DE DONNÉES 20NEWSGROUPS

Les exemples de cette section se basent sur un jeu de données standard nommé 20Newsgroups
(http://qwone.com/⁓jason/20Newsgroups/) qui fait partie de l’installation Scikit-learn. C’est un jeu de
données bien adapté à la présentation de plusieurs genres d’analyse de texte, et le site de référence donne
quelques détails à ce sujet.
Vous n’avez rien à faire pour pouvoir utiliser ce jeu parce que la librairie Scikit-learn le connaît déjà.
Cependant, lors de la première exécution de l’exemple, vous verrez apparaître un message d’avertissement
assez long. Rien de grave. Il suffit de patienter que les données soient téléchargées depuis le site. Observez
bien le côté gauche de la cellule de code dans IPython Notebook. L’indicateur va montrer un témoin [*].
Lorsque cette mention est remplacée par une valeur numérique, c’est que le téléchargement est terminé.
Notez que le message d’avertissement reste affiché jusqu’à la prochaine exécution.
Principe du sac de mots
Dans un sac de nœuds, des valeurs numériques représentent les mots, les fréquences des mots et les
positions des mots, ce qui permet d’effectuer des manipulations mathématiques pour trouver des
motifs remarquables dans la structure. Les sacs de mots ne tiennent pas compte de la grammaire, ni
même de l’ordre dans lequel les mots apparaissent. L’objectif est de simplifier le texte pour en
permettre une analyse aisée.
La création d’un sac de mots utilise les technologies NLP de traitement du langage naturel (Natural
Language Processing) et de recherche d’informations IR (Information Retrieval). Avant d’appliquer
ces deux traitements, vous devez normalement supprimer les caractères spéciaux, par exemple les
balises de format HTML, les mots vides et éventuellement appliquer une racinisation (comme décrit
en début de chapitre). Pour notre exemple, nous allons partir du jeu de données 20 Newsgroups. Voici
donc comment accéder à ce jeu pour produire un sac de mots :

from sklearn.datasets import fetch_20newsgroups


from sklearn.feature_extraction.text import *

categories = [‘comp.graphics’, ‘misc.forsale’,


‘rec.autos’, ‘sci.space’]
twenty_train = fetch_20newsgroups(subset=’train’,
categories=categories,
shuffle=True,
random_state=42)

count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(twenty_train.data)

print(“Profil BOW:”, X_train_counts.shape)


caltech_idx = count_vect.vocabulary_[‘caltech’]
print(‘”Caltech”: %i’ % X_train_counts[0, caltech_idx])

Soyez vigilant si vous consultez des exemples disponibles en ligne, car nombreux sont ceux qui ne
disent pas clairement d’où proviennent leurs listes de catégories. Une telle liste, que vous pouvez
utiliser, est fournie par exemple par le site http://qwone.com/⁓jason/20Newsgroups/. Faites
confiance au site de référence dès que vous avez besoin de répondre à une question concernant les
catégories du jeu de données.
Dans notre exemple, nous appelons fetch_20newsgroups() pour récupérer le jeu de données et le
charger en mémoire. L’objet d’entraînement nommé twenty_train correspond à un banc (bunch). Cet
objet contient une liste de catégories et les données associées, mais ces données ne sont pas encore
distribuées en éléments tokens individuels. De plus, l’algorithme qui doit traiter les données n’est pas
encore entraîné.
C’est à partir de ce banc de données que nous allons pouvoir créer notre sac. Nous commençons par
donner une valeur entière qui sert d’index à chaque mot unique du jeu d’entraînement. Chaque
document reçoit lui aussi une valeur entière. Nous comptons ensuite le nombre d’occurrences des
mots dans chaque document, puis produisons la liste de documents et comptons les paires, ce qui
permet de savoir quels mots apparaissent à quelle fréquence dans chacun des documents.
Certains des mots de la liste principale n’apparaissent pas dans certains documents, ce qui
correspond à un jeu de données creux à haute dimension. La matrice scipy.sparse est une structure
dans laquelle vous allez pouvoir stocker tous les éléments différents de zéro de la liste afin
d’économiser l’espace mémoire. Avec l’appel à count_vect.fit_transform(), nous stockons le sac
de mots produits ainsi dans X_train_counts. Nous pouvons donc ensuite afficher le nombre d’entrées
créées grâce à la propriété shape ainsi que le nombre d’occurrences du mot « Caltech » dans le
premier document :

BOW shape: (2356, 34750)


"Caltech": 3

Utilisation des n-grammes


Dans un texte à analyser, un n-gramme correspond à une séquence d’éléments adjacents. Ces
éléments peuvent être des phonèmes, des syllabes, des lettres de l’alphabet, des mots complets ou
des paires de bases. La lettre n dans n-gramme correspond à la taille. Un n-gramme avec une taille
de 1 est un unigramme. Dans l’exemple, nous utiliserons un trigramme. Les n-grammes servent sous
forme de probabilités pour par exemple prévoir quelle sera la prochaine séquence d’une série. Ils sont
extrêmement utiles, par exemple dans les moteurs de recherche et les mécanismes d’aide à la saisie
qui vous proposent le prochain mot à saisir en fonction des dernières lettres fournies. Cette technique
de prédiction connaît beaucoup d’autres domaines d’emploi, par exemple, le séquençage d’ADN et la
compression de données. Voyons comment créer un n-gramme à partir du jeu de
données 20 Newsgroups.
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import *

categories = [‘sci.space’]

twenty_train = fetch_20newsgroups(subset=’train’,
categories=categories,
remove=(‘headers’,
‘footers’,
‘quotes’),
shuffle=True,
random_state=42)

count_chars = CountVectorizer(analyzer=’char_wb’,
ngram_range=(3,3),
max_features=10)

count_chars.fit(twenty_train[‘data’])

count_words = CountVectorizer(analyzer=’word’,
ngram_range=(2,2),
max_features=10,
stop_words=’english’)

count_words.fit(twenty_train[‘data’])

X = count_chars.transform(twenty_train.data)

print(count_words.get_feature_names())
print(X[1].todense())
print(count_words.get_feature_names())

Comme dans le précédent exemple, nous commençons par récupérer le jeu de données pour le placer
dans un banc. En revanche, la vectorisation est réalisée différemment en raison des paramètres.
Ici, le paramètre nommé analyzer décide de la façon dont l’application doit créer les n-grammes.
Vous avez le choix entre des mots (word), des caractères (char) ou les caractères des extrémités des
mots, word boundaries (char_wb). Le paramètre nommé ngram_range attend d’abord deux entrées
sous forme d’un tuple : la taille minimale du n-gramme et sa taille maximale. Le troisième paramètre
max_features stipule combien de caractéristiques doivent être envoyées par la vectorisation. Dans le
second appel à la vectorisation, nous utilisons le paramètre stop_words pour supprimer les mots
vides (nous avons vu ce processus en début de chapitre). Après cette étape, les données sont adaptées
à l’algorithme de transformation.
L’exemple affiche trois types de résultats. Le premier montre les dix trigrammes de caractères
principaux du document. Le deuxième montre les détails du n-gramme du premier document avec la
fréquence des dix principaux n-grammes. Le troisième affichage montre les dix trigrammes principaux
pour les mots.

[‘anonymous ftp’, ‘commercial space’, ‘gamma ray’, ‘nasa gov’, ‘national


space’, ‘remote sensing’, ‘sci space’, ‘space shuttle’, ‘space station’,
‘washington dc’]
[[0 0 2 5 1 4 2 2 0 5]]
[‘anonymous ftp’, ‘commercial space’, ‘gamma ray’, ‘nasa gov’, ‘national
space’, ‘remote sensing’, ‘sci space’, ‘space shuttle’, ‘space station’,
‘washington dc’]

Transformations TF-IDF
La technique TF-IDF, de l’anglais Term Frequency times Inverse Document Frequency est une
opération de pondération : elle permet de distinguer plus aisément un document parmi plusieurs à
partir d’un terme détecté souvent dans toute la série de documents. En théorie, plus un terme est
fréquent dans un document, plus il est important pour ce document (si l’on exempte les mots vides).
Mais la mesure est faussée d’une part par la taille du document relativement aux autres, et d’autre
part par la fréquence du même terme dans les autres documents.
En effet, ce n’est pas parce qu’un mot apparaît souvent dans un document que ce terme est crucial
pour comprendre ou deviner la nature du document. Les mots vides sont très fréquents par définition,
avec à peu près la même fréquence dans tous les documents. Si vous analysez par exemple une série
de documents sur la science-fiction, comme celui de la série 20 Newsgroups, vous constaterez que la
plupart utilisent le terme UFO pour parler des OVNI. L’acronyme UFO ne permet donc pas de
différencier les documents. De plus, les documents plus longs auront un plus grand nombre de fois le
même terme sans que cela ait un sens intéressant.
En fait, un mot assez rare mais dans un seul document ou seulement quelques-uns pourront mieux
servir de discriminant de la nature du document. Si vous traitez par exemple des documents qui
parlent de science-fiction et aussi de voitures, l’acronyme UFO sera bien plus discriminant parce qu’il
n’appartient (pour le moment) qu’à un seul des deux sujets abordés.
Les moteurs de recherche ont sans cesse besoin de soupeser les mots des documents pour savoir s’ils
sont importants. Les mots ayant le plus grand poids vont servir à indexer chaque document, ce qui
permettra de le trouver aisément lorsque vous ferez une recherche avec ces mots. Vous devinez
pourquoi la transformation TF-IDF est assez souvent utilisée dans les moteurs de recherche.
Entrons dans les détails. La partie TF cherche à connaître la fréquence d’un terme dans un document
et la partie IDF détermine l’importance, le poids, du terme en calculant l’inverse de la fréquence de
ce terme dans tous les documents. Une valeur IDF élevée dénote un terme rare, la valeur TF-IDF
finale sera donc grande. Une valeur IDF faible correspond à un mot fréquent, ce qui donnera un score
TF-IDF faible. Plusieurs calculs sont disponibles à l’adresse http://www.tfidf.com/. Voici un
exemple de calcul de TF-IDF en Python :

from sklearn.datasets import fetch_20newsgroups


from sklearn.feature_extraction.text import *

categories = [‘comp.graphics’, ‘misc.forsale’,


‘rec.autos’, ‘sci.space’]
twenty_train = fetch_20newsgroups(subset=’train’,
categories=categories,
shuffle=True,
random_state=42)

count_vect = CountVectorizer()
X_train_counts = count_vect.fit_transform(
twenty_train.data)

tfidf = TfidfTransformer().fit(X_train_counts)
X_train_tfidf = tfidf.transform(X_train_counts)

caltech_idx = count_vect.vocabulary_[‘caltech’]
print(‘Score de "Caltech" dans un BOW:’)
print(‘count: %0.3f’ % X_train_counts[0, caltech_idx])
print(‘TF-IDF: %0.3f’ % X_train_tfidf[0, caltech_idx])

Comme dans les deux exemples précédents, nous commençons par récupérer le jeu de données, puis
créons un sac de mots (nous savons maintenant exploiter un tel sac).
Nous appelons TfidfTransformer() pour convertir les documents bruts en une matrice de
caractéristiques TF-IDF. Il n’est pas visible dans l’exemple, mais nous profitons de la valeur par défaut
du paramètre use_idf qui active une repondération de la fréquence de document inverse. Les
données une fois vectorisées sont adaptées à l’algorithme de transformation. Nous appelons ensuite
tfidf. transform() pour déclencher la transformation. Voici le résultat affiché par l’exemple :

Score de "Caltech" dans un BOW:


count: 3.000
TF-IDF: 0.123

Pour saisir la relation entre comptage d’occurrences et TF-IDF, il suffit de calculer le nombre moyen
d’occurrences et la moyenne de la valeur TF-IDF :

import numpy as np
count = np.mean(X_train_counts[X_train_counts>0])
tfif = np.mean(X_train_tfidf[X_train_tfidf>0])
print(‘moyenne de count : %0.3f’ % np.mean(count))
print(‘moyenne de TF-IDF: %0.3f’ % np.mean(tfif))

Les résultats montrent que quelle que soit votre manière de compter, par occurrences dans le premier
document ou par TF-IDF, la valeur sera toujours le double de la moyenne des mots. Cela permet de
comprendre qu’il s’agit d’un mot-clé pour modéliser le texte.

moyenne de count : 1.698


moyenne de TF-IDF: 0.064

La technique TF-IDF permet donc de repérer les termes ou n-grammes les plus importants et
d’exclure les moins importants. C’est également un traitement d’entrée très utile pour les modèles
linéaires, qui fonctionnent mieux avec des scores TF-IDF qu’avec des comptages de mots. Passée cette
étape, vous pouvez procéder à l’entraînement d’un classificateur pour lancer différentes analyses.
Mais ne vous inquiétez pas de cette partie du processus pour l’instant. Nous présenterons les
classificateurs dans les Chapitres 12 et 15, et verrons un exemple complet dans le Chapitre 17.

Traitement de données graphiques


Supposons un réseau de points de données interconnectés, par exemple un ensemble de pages Web
reliées par des hyperliens. Chaque point correspond à un nœud, non nécessairement lié à tous les
autres. Il est donc intéressant d’analyser ces connexions. En datalogie, on peut ainsi réaliser des
choses très intéressantes, comme la recherche du meilleur trajet pour aller du domicile au travail en
tenant compte des différentes routes. Voyons comment vous pouvez exploiter un graphe et réaliser
quelques actions élémentaires.

Matrices d’adjacence
Une matrice d’adjacence incarne les connexions entre les nœuds d’un graphe. Chaque lien entre deux
nœuds correspond à une valeur supérieure à 0 dans la matrice. La représentation exacte des
connexions dépend du fait que le graphe est de type orienté (le sens des connexions est géré) ou non
orienté.
Le problème de la plupart des exemples en ligne est que les auteurs utilisent un petit nombre de
données pour pouvoir simplifier les explications. Dans le monde réel, vous allez faire face à des
graphes beaucoup plus complexes et vous ne pourrez pas en effectuer une analyse simplement par
visualisation. Songez seulement au nombre de nœuds correspondant aux carrefours dans une ville de
taille moyenne, les rues étant les liens. Dès qu’un graphe devient dense, il est impossible de découvrir
des motifs remarquables par simple visualisation. Cela correspond au problème de la touffe de poils
(hairball).
Une approche pour analyser une matrice adjacente consiste à les trier d’une façon appropriée. Vous
pouvez par exemple trier les données en fonction d’une propriété autre que leurs liens. Le graphe des
rues d’une ville pourrait être classé dans l’ordre chronologique de dernière réfection de chaque rue,
ce qui permettrait de créer un trajet tenant compte de l’état des chaussées. Autrement dit, pour bien
bénéficier des données d’un graphe, il s’agit de savoir manipuler l’organisation interne des données.

Découverte de NetworkX
L’exploration des graphes serait fastidieuse s’il vous fallait écrire la totalité du code source. Fort
heureusement, vous disposez du paquetage NetworkX pour Python. Il simplifie la création, la
manipulation et l’étude de la structure, de la dynamique et des fonctions d’un réseau (d’un graphe)
complexe. Dans ce livre, nous ne verrons que les graphes, mais le paquetage sait aussi gérer les
digraphes et les multigraphes.
L’objectif principal de NetworkX est d’éviter l’apparition de cette touffe de poils (plat de nouilles)
inexploitable. Grâce à de simples appels de fonction, vous restez à l’abri de la complexité du
traitement d’un graphe et d’une matrice d’adjacence. L’exemple suivant crée une matrice élémentaire
à partir d’un des graphes fournis avec le paquetage NetworkX :

import networkx as nx

G = nx.cycle_graph(10)
A = nx.adjacency_matrix(G)

print(A.todense())

Nous commençons bien sûr par importer le paquetage puis créons un graphe à partir du modèle
cycle_graph(). Il comporte 10 nœuds. Nous appelons adja-cency_matrix() pour créer la matrice,
puis affichons son contenu :

[[0 1 0 0 0 0 0 0 0 1]
[1 0 1 0 0 0 0 0 0 0]
[0 1 0 1 0 0 0 0 0 0]
[0 0 1 0 1 0 0 0 0 0]
[0 0 0 1 0 1 0 0 0 0]
[0 0 0 0 1 0 1 0 0 0]
[0 0 0 0 0 1 0 1 0 0]
[0 0 0 0 0 0 1 0 1 0]
[0 0 0 0 0 0 0 1 0 1]
[1 0 0 0 0 0 0 0 1 0]]

Vous pouvez faire des tests sans devoir créer vos propres graphes car le site de NetworkX propose un
certain nombre de graphes standard, tous utilisables dans IPython. Voyez la page
https://networkx.github.io/documentation/latest/reference/generators.html.
Il n’est pas inutile de générer une représentation graphique de notre graphe, comme le fait l’exemple
suivant. La Figure 8.1 montre le résultat.

import matplotlib.pyplot as plt


%matplotlib inline
nx.draw_networkx(G)
plt.show()

Figure 8.1 : Tracé du graphe originel.

Nous pouvons ajouter un lien entre les nœuds 1 et 5 en utilisant la fonction add_ edge(). Le résultat
est visible dans la Figure 8.2.

G.add_edge(1,5)
nx.draw_networkx(G)
plt.show()

Figure 8.2 : Affichage du graphe après ajout d’un lien.


Chapitre 9
Discrétisation et tableaux
DANS CE CHAPITRE :
» Mise en perspective des problèmes de datalogie

» Création de caractéristiques et discrétisation

» Traitement de tableaux

es chapitres précédents constituent une partie préparatoire. Vous savez maintenant réaliser
L quelques tâches indispensables avec Python et avez pris le temps d’utiliser plusieurs outils
utiles en datalogie. Après cette première phase, prenons un peu de recul pour découvrir ce
qu’il faut prendre en compte pour résoudre un problème de science des données.

Ce chapitre ne représente pas la fin de votre voyage. Si on compare les chapitres précédents à
la préparation des valises, à la création des réservations et au choix de l’itinéraire, ce chapitre
représente le trajet jusqu’à l’aéroport. Vous commencez à voir globalement les choses se
mettre en place.
Nous allons d’abord passer en revue les points à prendre en compte pour résoudre un
problème de datalogie. Vous ne pouvez en effet pas plonger la tête la première dans une
analyse. Il faut d’abord comprendre le problème et considérer les ressources disponibles en
termes de données d’entrée, d’algorithmes et de ressources informatiques. En considérant le
contexte du problème, vous allez mieux le comprendre et mieux définir pourquoi les données
dont vous disposez le concerne. En effet, le contact est essentiel parce que comme dans les
langues humaines, il change la signification aussi bien du problème que des données. Par
exemple, si vous dites que vous avez des roses rouges, le sens ne sera pas le même selon que
vous vous adressiez à votre compagne ou compagnon, ou bien à un collègue qui, comme vous,
aime jardiner. La rose rouge est la donnée, le contexte est la personne à laquelle vous en
parlez. Déclarer « J’ai une rose rouge » hors contexte n’a pas de sens pratique. Les données
n’ont en elles-mêmes pas de signification car elles ne permettent de répondre à une question
que lorsque vous connaissez leur contexte. Ainsi, lorsque vous dites « J’ai des données » , se
pose tout de suite la question « Qu’est-ce que signifient ces données ? » .
Vous travaillerez à partir d’un ou de plusieurs jeux de données, par exemple des tables en deux
dimensions constituées de lignes horizontales qui sont les cas ou observations et de colonnes
qui sont les caractéristiques, ou variables en statistiques. Ce sont ces variables utilisées dans
vos analyses qui vont déterminer vos résultats. Il est donc essentiel dans la phase de
conception de votre solution de bien déterminer quelles caractéristiques vous voulez pouvoir
obtenir à partir des données source et comment vous voulez les transformer.
Dans la dernière section du chapitre, nous verrons quelques cas pratiques. Il est souvent
possible d’utiliser plusieurs approches, mais lorsque vous avez un grand volume de données,
privilégiez la rapidité. En utilisant des tableaux et des matrices pour certaines opérations, vous
remarquerez qu’il faut parfois patienter, tant que vous n’avez pas découvert certaines
techniques et astuces. Il n’est d’ailleurs jamais trop tôt pour au moins connaître l’existence de
certaines de ces techniques ; vous les retrouverez dans les chapitres ultérieurs, lorsque vous
commencerez à prendre en main l’ensemble du processus, et verrez que c’est une véritable
magie qui va permettre de faire parler les données d’une manière que vous ne soupçonnez pas
encore.

Établissement du contexte
Nous avons dit qu’il fallait trouver le contexte pour déployer une solution de datalogie viable. Il
s’agit d’une science appliquée et une approche abstraite ne conviendra sans doute pas. Bien
sûr, vous pourriez par vanité adopter un cluster Hadoop ou mettre en place un réseau neuronal
complexe et en faire la démonstration devant vos collègues. Il n’est pas sûr que ce genre de
solution ambitieuse résolve votre problème. Vous devez vérifier si un algorithme est bien
approprié, ou si la transformation des données que vous prévoyez convient effectivement. C’est
tout un art que d’examiner le problème de façon critique ainsi que les ressources, puis de créer
un environnement permettant d’obtenir une solution désirable.
L’expression solution désirable souligne le fait que vous pourriez tout à fait aboutir à des
solutions qui ne le sont pas, du fait qu’elles ne répondent pas à la demande, ou bien qu’elles y
répondent, mais en consommant trop de temps et de ressources. Voyons les grandes lignes du
processus permettant de créer le contexte du problème et des données.

Évaluer un problème de datalogie


Face à un problème de datalogie, vous devez commencer par considérer le but et les
ressources disponibles. Ces ressources comprennent les données d’entrée, les ressources de
traitement (en puissance et en espace mémoire disponibles) et l’espace de stockage. Dans le
monde réel, vous ne verrez jamais quelqu’un vous livrer des données d’entrée prêtes à
l’emploi, en vous demandant de les analyser. Ce sera à vous de déterminer toutes les étapes de
la solution. Voici les aspects à prendre en compte lors de votre première évaluation du
problème :
» La disponibilité des données en quantité et en qualité. Pensez à tenir compte des
éventuels biais d’interprétations qui peuvent altérer les caractéristiques et le contenu. Les
données ne sont pas des vérités nues, mais seulement des vérités relatives qui contribuent
plus ou moins à la résolution d’un problème (nous y revenons en détail dans l’encadré qui
suit). Cherchez toujours à évaluer la fiabilité des données d’entrée et soyez critique.
» Les méthodes disponibles pour l’analyse. Celles-ci peuvent être simples ou
complexes. Tenez compte de votre maîtrise d’une technique ou d’une autre. Commencez
par des approches simples, et ne vous agrippez pas systématiquement à une méthode. En
datalogie, n’espérez pas de raccourcis ou de martingales.
» Les questions que votre analyse doit résoudre et les moyens dont vous disposez
pour vérifier que vous avez apporté une bonne réponse. « Ce qui ne peut pas être
mesuré ne peut pas être amélioré » avait dit Lord Kelvin. Si vous pouvez mesurer vos
performances, vous pouvez déterminer l’impact de vos efforts, y compris en termes
pécuniaires. Sans compter que vos commanditaires seront heureux d’apprendre que vous
avez une bonne idée de ce qu’il faut faire et des avantages que votre analyse va apporter.
CINQ CAUSES D’INCERTITUDE DES DONNÉES

Les humains sont habitués à considérer les données pour ce qu’elles sont, la plupart du temps : de
simples opinions. D’ailleurs, il arrive que les gens tordent la vérité des données au point de les rendre
inutilisables ; elles deviennent des incertitudes. Un ordinateur n’est pas capable de distinguer le vrai
du faux : il ne voit que des données. Lors de vos analyses, vous devez donc évaluer le degré de
véracité des données. Une chose que vous ferez dans tous les cas est de traquer les données
aberrantes pour les supprimer, mais cette technique ne suffit pas toujours à résoudre le problème. Un
humain qui exploite ces données d’entrée risque de déduire une vérité à partir de ces incertitudes.
Voici les cinq cas de données non fiables que vous pouvez rencontrer, en utilisant un accident de la
circulation en guise d’illustration :
» Embellissement : cette catégorie de dévoiement de la vérité est le fruit d’une tentative
volontaire de remplacer une information fiable par une information non fiable. Par exemple, lors
de la rédaction du constat amiable d’accident, un individu peut prétendre qu’il a été ébloui par le
soleil, ce qui l’a empêché d’éviter la collision. En réalité, cette personne était peut-être distraite,
par exemple en pensant à ce qu’elle allait faire en rentrant chez elle. Personne ne pourra la
contredire, et elle ne sera pas autant sanctionnée. Le résultat est que les données d’entrée sont
contaminées.
» Omission : l’introduction d’incertitude par omission se produit lorsqu’une personne ne ment sur
aucun point soulevé, mais oublie de mentionner un point essentiel pouvant changer la
perception globale de la situation. Si nous reprenons notre accident, supposons que la personne
ait embouti un sanglier, ce qui a endommagé sa voiture. Elle déclare à juste titre que la
chaussée était humide, que cela s’était passé au crépuscule et qu’elle avait un peu tardé à
freiner, sans compter que le sanglier a surgi d’un bosquet. La conclusion serait qu’il s’agit d’un
simple accident non responsable. Mais la personne a oublié de mentionner qu’elle était en train
d’envoyer un SMS. Si les services de police avaient été informés de cela, les causes de l’accident
auraient été différentes. Le conducteur aurait sans doute écopé d’une contravention et
l’assurance aurait renâclé à rembourser les réparations. Les données saisies dans la base de la
police n’auraient pas été les mêmes.
» Perspective : ce genre d’altération se produit lorsque plusieurs témoins voient le même
incident. Prenons l’exemple d’un accident impliquant un piéton et une voiture. Nous aurons trois
perspectives différentes : celle du conducteur, celle du piéton et celle du passant qui a tout vu.
L’officier de police obtiendrait trois versions différentes, en supposant bien sûr que chacune des
trois personnes dise la vérité. Ces divergences sont presque toujours la règle et le rapport que va
dresser l’officier va constituer une moyenne des trois points de vue, enrichie de son expérience
personnelle. Ce rapport sera donc proche de la vérité, mais proche seulement. Il y a lieu de
considérer les différents points de vue. Celui du conducteur lui permet de dire ce qu’il voyait sur
son tableau de bord et de dire qu’elle était la condition du véhicule à ce moment. Les deux
autres personnes ne connaissent pas ces informations. Le piéton qui a été heurté peut indiquer
l’expression du visage du conducteur au moment du choc. Enfin, le témoin est le mieux placé
pour savoir si le conducteur a tenté de l’éviter. Chacune des personnes doit faire sa déclaration
sans pouvoir tirer avantage des données qu’elle ne connaît pas.
» Biais : on parle de biais d’interprétation lorsqu’une personne peut voir la vérité mais que des
convictions et/ou des croyances personnelles l’obligent à la déformer. Toujours dans l’exemple de
l’accident, un conducteur peut tellement se concentrer sur sa trajectoire qu’il ne parvient même
plus à voir surgir le sanglier et n’a plus le temps de réagir. Cette catégorie de contre-vérités est
difficile à appréhender. Il est possible que le conducteur n’ait vraiment pas pu voir le sanglier car
il était tapi dans un bosquet. Il peut aussi ne pas l’avoir vu par inattention, alors qu’il arrivait en
terrain découvert. Autrement dit, la question n’est pas de savoir si le conducteur a vu le sanglier,
mais pourquoi il ne l’a pas vu. Il arrive que la vérification des sources d’une incertitude de biais
prenne de l’importance lorsqu’il s’agit de créer un algorithme qui doit éviter ce genre de sources
de données imprécises.
» Cadre de référence : cette catégorie ne correspond normalement pas à une erreur volontaire
ou pas, mais à un problème de compréhension. Une incertitude de cadre de référence est celle
qui se produit lorsqu’une personne décrit un événement ou un accident, mais que son
interlocuteur n’a aucune expérience avec ce genre d’événements, ce qui l’empêche de
comprendre la situation. Les quiproquos qui s’ensuivent sont abondamment exploités dans les
comédies. Il n’est vraiment pas facile de faire comprendre quelque chose à une personne
lorsqu’elle n’a pas de connaissances et d’expérience dans le domaine, c’est-à-dire de cadre de
référence.

Recherche de solutions
La datalogie est un domaine de connaissances complexes à l’intersection entre sciences
informatiques, mathématiques, statistiques et commerce. Rares sont ceux qui peuvent
maîtriser tous ces domaines à la fois. Vous devez chercher si quelqu’un d’autre n’a pas déjà eu
à faire face au même problème pour ne pas réinventer la roue. À partir du moment où vous
avez créé le contexte de votre projet, vous savez mieux ce que vous devez chercher et pouvez
puiser dans différentes sources.
» Lisez la documentation de Python. Vous trouverez peut-être des exemples qui vous
mettront sur une piste. Vous trouverez beaucoup de documentations en anglais avec des
exemples de datalogie sur les sites des librairies NumPy, SciPy, pandas et surtout Scikit-
learn. Voici les adresses de ces sites :
https://docs.scipy.org/doc/numpy/user/
https://docs.scipy.org/doc/
https://pandas.pydata.org/pandas-docs/version/0.23.2/
https://scikit-learn.org/stable/user_guide.html
» Cherchez sur le Web comment d’autres ont résolu le même genre de problème.
Cherchez par exemple des sites tels que Quora, Stack Overflow ou Cross Validated.
» Lisez les études des chercheurs. Vous pouvez chercher, par exemple, Google Scholar
ou Microsoft Academics. Vous y trouverez des articles qui expliquent comment préparer les
données ou donnent les détails des algorithmes les plus adaptés à chaque problème.

Bien que ce soit évident, votre solution doit d’abord répondre au problème. Lors de votre
recherche de solutions existantes, vous en trouverez qui semblent prometteuses, mais que
vous ne parviendrez pas à bien appliquer à votre situation car le contexte n’est pas exactement
le même. Votre jeu de données est peut-être incomplet ou pas suffisamment vaste. L’approche
d’analyse que vous choisissez peut ne pas bien répondre à la question posée, ou de manière
imprécise. N’hésitez pas à repartir dans vos recherches tout en progressant, au fur et à mesure
de vos tests, afin d’évaluer d’autres solutions compatibles et vos contraintes précises.

Formulation d’une hypothèse


Il arrive un moment où vous pensez avoir tout ce qu’il faut pour vous lancer dans la résolution
du problème. Mais n’allez pas si vite en besogne. Ce que vous avez est une hypothèse, pas une
solution. Vous devez prouver son efficacité de façon scientifique. Pour créer et tester une
hypothèse, vous devez entraîner un modèle avec un jeu de données d’entraînement puis le
tester avec un jeu totalement différent. Les chapitres suivants du livre consacrent beaucoup de
temps à décrire le processus d’entraînement et de test des algorithmes d’analyse. Nous
n’entrons pas dans ces détails pour le moment.

Préparation des données


Une fois que vous connaissez le genre de solution dans ses grandes lignes, vous devez
connaître les données d’entrée qui seront requises pour votre algorithme. Au départ, ces
données se présentent dans plusieurs formats, en provenance de plusieurs sources, et
certaines sont manquantes. Les personnes qui ont produit les données, et donc les
caractéristiques, avaient sans doute en vue une tout autre finalité, par exemple de comptabilité
ou de marketing. Il vous faut transformer ces données pour les rendre efficacement
exploitables par votre algorithme. Il s’agit donc de les préparer. Vous devrez chercher les
données manquantes, créer de nouvelles caractéristiques et manipuler le jeu de données pour
que l’algorithme puisse produire une prédiction.

L’art de créer des caractéristiques


Les caractéristiques sont les contenus de vos colonnes dans le jeu de données. Il est essentiel
de bien choisir ces contenus, qui peuvent ne pas ressembler aux données du jeu d’entrée. En
effet, les données brutes dont vous partez se présentent peut-être sous une forme qui
entraînerait une analyse inexacte, voire vous empêcherait d’aboutir à un résultat exploitable,
parce qu’elles ne correspondent pas à l’algorithme envisagé. Elles peuvent par exemple
contenir trop de redondance entre plusieurs variables (problème de corrélation multivariante).
Le travail de création des caractéristiques consiste à faire en sorte que les colonnes soient les
mieux prêtes à subir une analyse. Découvrons les principes de cette création. (Nous verrons
dans les chapitres suivants plusieurs exemples de création de caractéristiques.)
Nature de la création de caractéristiques
La création de caractéristiques pourrait paraître une sorte d’activité magique ou obscure, alors
qu’elle a des fondements solides en mathématiques. L’objectif est de transformer des données
d’entrée. Un exemple trivial consiste à transformer les chaînes de caractères qui représentent
(symbolisent) des valeurs numériques en véritables valeurs de type numérique. Le but premier
de la création de caractéristique est donc de rendre les algorithmes d’analyse plus efficaces
qu’en les faisant travailler sur les données de départ.
Cette transformation est souvent fastidieuse. Elle suppose de combiner les valeurs d’une
manière ou d’une autre et de leur appliquer des opérations mathématiques. Les données
initiales se présentent dans une grande variété de formats, et ce n’est que grâce à la
transformation que vous pourrez détecter les motifs remarquables. Le site de concours de
datalogie Kaggle (racheté par Google en 2017, https://www.kaggle.com/c/march-machine-
learning-mania-2015) en offre un bon exemple. Un des concours consistait à déterminer le
vainqueur du tournoi de basket-ball NCAA. Il s’agissait de collecter différentes données à
partir des matchs (distance à parcourir pour se rendre à chaque match, absence de certains
joueurs clés, etc.). Cela vous donne une idée de ce qu’il faut prévoir pour créer des
caractéristiques.
La création de caractéristiques est véritablement autant un art qu’une science ; chacun a son
avis au sujet de la meilleure façon d’y travailler. Dans ce livre, nous vous fournissons les
informations de base ainsi qu’un certain nombre d’exemples, mais les techniques plus évoluées
sont laissées à votre sens de l’expérimentation. Le professeur Pedro Domingos, de l’université
de Washington, a rappelé, comme d’autres confrères, que l’ingénierie des caractéristiques est
sans doute le facteur le plus important pour décider du succès d’un projet d’apprentissage
machine et que rien ne peut remplacer l’intelligence que vous y déployez
(https://homes.cs.washington.edu/⁓pedrod/papers/cacm12.pdf).

Combinaison de variables
Vos données d’entrée se présentent très rarement dans le format approprié à un algorithme.
Prenons une situation concrète : l’installation de plans de travail de cuisine. Le poids d’un plan
oblige à planifier l’affectation de 1, 2 ou 3 installateurs sur chaque chantier. Vous disposez de
deux tables d’entrée. La première contient les dimensions et les matériaux des différents plans
disponibles. La seconde contient les densités par matériau. Une seule personne ne suffit que
pour installer un plan de travail dont le poids ne dépasse pas certaines valeurs, et de plus, dont
la longueur ne dépasse pas une certaine limite. Votre objectif est de produire des prédictions
pour que l’entreprise organise au mieux la distribution de ses installateurs sur les différents
chantiers.
Vous allez créer un jeu de données à deux dimensions par combinaison de variables. Il n’y aura
que deux caractéristiques dans le jeu résultant. La première va informer sur les longueurs des
plans de travail. Une seule personne peut transporter un plan de 2 m au maximum, dans
certains matériaux. Il faudra deux personnes pour installer un plan plus long. L’autre
caractéristique va correspondre au poids (à la masse) du plan. Un plan de 2 m de long avec
une épaisseur de 28 cm peut être installé par une seule personne dans certains matériaux,
mais pas dans d’autres. Dans ce cas, il vous faudra deux personnes et si le plan est plus long,
trois.
La première caractéristique est facile à générer. Il suffit de récupérer les longueurs des
différents plans de travail du catalogue. La seconde caractéristique suppose de réaliser un
calcul à partir des variables des deux tables :

Longueur * Largeur * Épaisseur * Densité

Votre jeu de données résultant permettra de connaître le poids pour chaque longueur de plan
dans chacun des matériaux disponibles. Cela vous permet de produire un modèle pour prédire,
donc planifier, le nombre d’installateurs.

Regroupement (binning) et discrétisation


Pour certains types d’analyse, vous aurez besoin de distribuer les valeurs numériques en
plusieurs catégories ou classes. Par exemple, si vous partez d’un jeu d’entrée listant des
individus âgés de 0 à 80 ans, pour appliquer un algorithme statistique tel que celui appelé
Naïve Bayes, vous avez intérêt à pouvoir accéder à vos variables sous forme de classes
de 10 ans en 10 ans. Cette simplification des données d’entrée en tranches correspond à
l’opération de regroupement (binning). Chaque groupe ou étage devient une catégorie
numérique exploitable.
Le regroupement favorise une meilleure précision des modèles en limitant le bruit ou en
réduisant la non-linéarité du modèle. L’opération permet aussi d’identifier plus facilement les
aberrants, c’est-à-dire les valeurs clairement en dehors de la plage vraisemblable, ainsi que les
valeurs numériques manquantes ou invalides.
L’opération de regroupement ne s’applique qu’à des valeurs numériques indépendantes. La
discrimination est plus complexe, car elle permet de réunir des combinaisons de valeurs de
plusieurs caractéristiques dans des baquets (buckets), ce qui limite le nombre d’états possibles
dans chacun des baquets. À la différence du regroupement, la discrétisation fonctionne aussi
bien avec des valeurs numériques qu’avec des valeurs de type chaîne de caractères. C’est une
méthode plus générale pour créer des catégories. Vous pouvez par exemple aboutir à une
discrétisation en tant que produit secondaire d’une analyse d’amas ou grappes (clustering).

Variables indicatrices
Une variable indicatrice est une variable booléenne, une caractéristique ne pouvant prendre
que la valeur 0 ou 1. C’est une sorte de pseudo-variable simplifiant l’analyse. Prenons comme
exemple un jeu de données qui distingue les individus majeurs des mineurs. Vous pouvez
remplacer la caractéristique correspondant à l’âge par une telle variable, et ainsi remplacer les
valeurs de 0 à 17 ans par la caractéristique booléenne 0 et les valeurs à partir de 18 ans par la
caractéristique 1.
Les variables indicatrices accélèrent les analyses et offre une plus grande précision pour la
création de catégories. En quelque sorte, elle remplace des niveaux de gris par du noir et
blanc. Une personne est soit majeure, soit mineure, après tout. Il s’agit donc d’une
simplification des données.

Distribution transformatrice
Une distribution consiste à organiser les valeurs d’une variable ou caractéristique permettant
de connaître la fréquence d’apparition des différentes valeurs pour mieux comprendre les
données. Il existe un grand nombre de distributions possibles et la plupart des algorithmes
savent les traiter (vous trouverez une galerie à l’adresse
https://www.itl.nist.gov/div898/handbook/eda/section3/eda366.htm). Il faut cependant
veiller à ce que l’algorithme soit compatible avec la distribution désirée.
Soyez particulièrement attentif devant les distributions trop uniformes ou biaisées car elles
sont difficiles à traiter pour plusieurs raisons. Au contraire, c’est la courbe en forme de cloche
qui sera votre amie. Dès que vous voyez qu’une distribution s’éloigne de cette distribution en
cloche, songez à lui appliquer une transformation.
Lorsque vous traitez une distribution, vous constaterez de temps à autre que les valeurs
semblent faussées, et l’algorithme que vous y appliquez ne produira pas un résultat conforme à
vos attentes. En transformant la distribution, vous appliquez une fonction à ces valeurs, par
exemple pour corriger leur dérive. La transformation rend la distribution plus exploitable,
faisant de votre jeu d’entrée une distribution normale. Voici quelques transformations qu’il est
conseillé de toujours appliquer à vos caractéristiques numériques :
» logarithmes np.log(x) et exponentielles np.exp(x) ;
» inverse 1/X, racine carrée np.sqrt(x) et racine cubique x**(1.0/3.0) ;
» transformations polynomiales telles que x**2, x**3, etc.

Opérations sur les tableaux et les matrices


Une catégorie d’opérations élémentaire pour manipuler les données consiste à les placer dans
un tableau ou dans une matrice, puis à appliquer des techniques mathématiques standard.
Cela vous permet de reformater les données pour pouvoir ensuite appliquer les opérations au
niveau de chaque observation, par exemple dans des boucles de répétition. Vous profitez ainsi
au mieux de l’architecture matérielle de traitement disponible et des routines d’algèbre
linéaire très optimisées qui sont gravées dans les processeurs. Ces routines sont de ce fait
disponibles dans tous les systèmes d’exploitation. Plus le volume de données est important,
plus vous allez gagner du temps avec ce reformatage. En outre, ces techniques économisent de
nombreuses lignes de code source Python. Voyons donc comment travailler avec des tableaux
et des matrices en datalogie.
Le fichier du calepin des exemples de ce chapitre correspond au fichier PYDASC_09.

Vectorisation
Le processeur de votre ordinateur est capable de réaliser de façon très efficace certains
calculs. Vous allez en tirer profit si les données lui sont présentées dans un bon format. La
structure de stockage de données de NumPy nommée ndarray permet de mettre en place une
table de données à plusieurs dimensions. Vous pouvez ainsi construire un cube, voire un
hypercube lorsqu’il y a plus de trois dimensions.
Les calculs deviennent simples et rapides avec ce genre de tableau. Dans l’exemple suivant,
nous créons un jeu de données comprenant trois observations avec sept caractéristiques
(attributs) pour chacune. L’exemple calcule la valeur maximale de chaque observation puis la
soustrait à la valeur minimale pour produire la plage de valeurs.

import numpy as np
dataset = np.array([[2, 4, 6, 8, 3, 2, 5],
[7, 5, 3, 1, 6, 8, 0],
[1, 3, 2, 1, 0, 0, 8]])
print(np.max(dataset, axis=1) - np.min(dataset, axis=1))

L’instruction d’affichage obtient le maximum avec np.max() puis soustrait le minimum obtenu
par np.min(). Dans l’exemple, la valeur maximale de chaque observation vaut [8 8 8]et la
valeur minimale vaut [0 0 0]. Voici le résultat :

[6 8 8]

Arithmétique sur les vecteurs et les matrices


La plupart des fonctions offertes par la librairie NumPy pour les tableaux tirent avantage de la
vectorisation, ce qui la rend plus efficace que toute autre solution, y compris manuelle. Cette
vectorisation profite même aux opérations élémentaires que sont les additions et les divisions.
Il arrivera souvent que les données d’entrée ne soient pas dans le format désiré. Par exemple,
vous pouvez faire face à une liste de nombres qui sont en réalité des pourcentages, mais ont
été stockés sous forme de valeurs entières. Vous pouvez appliquer une opération simple pour
obtenir de véritables pourcentages comme ceci :

import numpy as np
a = np.array([15.0, 20.0, 22.0, 75.0, 40.0, 35.0])
a = a*.01
print(a)

Cet exemple commence par créer un tableau puis le remplit avec les valeurs entières qui
doivent devenir des pourcentages. Nous appliquons ensuite une multiplication par 0.01, ce qui
permet ensuite d’utiliser ces valeurs fractionnaires avec d’autres nombres, pour vous en servir
réellement en tant que pourcentages. Voici le résultat affiché :

[0.15 0.2 0.22 0.75 0.4 0.35]

Multiplication de vecteurs dans une matrice


L’addition et la multiplication de plusieurs valeurs avec plusieurs autres constituent une des
opérations les plus efficaces de la vectorisation dans une matrice. Avec NumPy, la
multiplication d’un vecteur avec une matrice est très simple, ce qui permet par exemple
d’estimer une valeur pour chaque observation en tant que somme pondérée de
caractéristiques. Voici un exemple :

import numpy as np
a = np.array([2, 4, 6, 8])
b = np.array([[1, 2, 3, 4],
[2, 3, 4, 5],
[3, 4, 5, 6],
[4, 5, 6, 7]])
c = np.dot(a, b)
print(c)
Le tableau défini en tant que vecteur doit l’être avant celui correspondant à la matrice, si vous
ne voulez pas provoquer d’erreur. Voici le résultat :

[ 60 80 100 120]

L’opération consiste à multiplier chacune des valeurs du premier tableau par la valeur de la
colonne correspondante dans la matrice. Autrement dit, la première valeur du tableau est
multipliée par la valeur de la première colonne en première ligne de la matrice. C’est ainsi que
la première valeur calculée est égale à 60, soit 2*1 + 4*2 + 6*3 + 8*4.

Multiplication de matrices
Vous pouvez également multiplier une matrice par une autre, le résultat correspondant à la
multiplication des lignes de la première matrice avec les colonnes de la seconde. Voici
comment procéder avec NumPy :

import numpy as np

a = np.array([[2, 4, 6, 8],
[1, 3, 5, 7]])
b = np.array ([[1, 2],
[2, 3],
[3, 4],
[4, 5]])
c = np.dot(a, b)
print(c)

Le résultat est une matrice de 2 sur 2. Dans l’exemple, cette matrice contiendra les valeurs
suivantes :

[[60 80]
[50 66]]

Chaque ligne de la première matrice est multipliée par chacune des colonnes de la seconde.
Par exemple, la valeur 50 de la seconde ligne des résultats correspond à la multiplication des
valeurs de la ligne 2 de la matrice a par celles de la colonne 1 de la matrice b, comme ceci :
1*1 + 3*2 + 5*3 + 7*4.
PARTIE 3
Visualisation des informations

DANS CETTE PARTIE :


» Découverte du grapheur MatPlotLib
» Composants d’une sortie graphique
» Création de différents styles de présentation
» Gestion des données géographiques
Chapitre 10
Visite guidée de MatPlotLib
DANS CE CHAPITRE :
» Création d’un diagramme simple

» Ajout de repères et traits

» Embellissement avec des styles et des couleurs

» Ajout de labels, d’annotations et de légendes

L a plupart des gens parviennent mieux à digérer des informations sous forme graphique que
sous forme textuelle. Un graphique ou diagramme aide à voir les relations entre les éléments
et à faire des comparaisons, car il s’agit d’être efficace dans vos moyens de communication.
Rien ne sert de passer des heures à préparer, analyser et restituer des données si vous êtes le
seul à pouvoir en tirer profit. Python facilite la création de diagrammes à partir de vos données
texte grâce à la librairie MatPlotLib, une émulation de l’application complète Matlab. Une
comparaison des deux est disponible à l’adresse
https://phillipmfeldman.org/Python/Advantages_of_Python_Over_Matlab.html.
Si vous connaissez déjà l’application MATLAB, vous maîtriserez rapidement MatPlotLib, car
cette librairie utilise le même concept de machine à changement d’état, et demande de définir
les éléments des diagrammes de la même façon. Certains considèrent même que MatPlotLib
est supérieur à MATLAB du fait que le nombre de lignes de code source à écrire est inférieur.
D’autres ont remarqué que le passage de MATLAB à MatPlotLib était facile. Vous pouvez tout à
fait combiner les deux outils : faire des expérimentations sur vos données avec MATLAB puis
créer votre application avec Python et MatPlotLib. C’est une question de goût.
Dans ce chapitre, nous allons découvrir ce qu’il est possible de faire avec MatPlotLib. Nous
nous servirons de cette librairie plusieurs fois dans la suite du livre, aussi ne réaliserons-nous
ici qu’une visite rapide mais cependant essentielle. De ce fait, si vous connaissez déjà MATLAB,
vous pourrez certainement parcourir rapidement ce chapitre, et même en ignorer certaines
sections. Dans tous les cas, la connaissance de ce chapitre est un prérequis pour l’utilisation de
MatPlotLib dans les chapitres suivants.
Le fichier archive des exemples de ce chapitre correspond à PYDASC_10. Nous avons expliqué
dans l’introduction comment récupérer cette archive.

Création d’un premier diagramme


Les diagrammes que nous allons avoir besoin de créer dans notre contexte sont des
représentations visuelles de données numériques. MatPlotLib propose un vaste choix de
diagrammes et d’histogrammes. Vous disposez bien sûr des diagrammes classiques que sont
les diagrammes à barres ou histogrammes, les diagrammes en lignes et les camemberts. Tout
comme avec MATLAB, vous pouvez créer des diagrammes à moustaches (boxplots), des
histogrammes d’erreur, etc. Une galerie montre les types disponibles à l’adresse
https://matplotlib.org/gallery.html. De plus, vous pouvez combiner les composants des
diagrammes selon vos besoins, quelle que soit la complexité des données. Voyons donc
comment créer un diagramme simple, mais sachez que vous avez bien plus de possibilités.

Définition du tracé (plot)


Un tracé va montrer graphiquement ce que vous avez défini numériquement. Il vous faut des
valeurs, le module matplotlib.pyplot et une idée claire de ce que vous voulez rendre visible.
Voyons un premier exemple.

import matplotlib.pyplot as plt


%matplotlib inline
valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]
plt.plot(range(1,11), valeurs)
plt.show()

Dans cet exemple, nous utilisons la fonction plt.plot() pour créer un tracé avec des valeurs
selon l’axe x entre 1 et 11 ; pour l’axe vertical y, nous puisons dans les variables du jeu de
données. En appelant plot.show(), nous faisons afficher le diagramme dans un encadré sous
la cellule de code (parfois dans une fenêtre indépendante). Voyez la Figure 10.1. Par défaut, le
style graphique est un diagramme à lignes. Nous verrons d’autres types de diagrammes dans
le Chapitre 11.

Figure 10.1 : Création d’un diagramme simple de type lignes.

Tracé de plusieurs lignes


Il vous arrivera souvent de devoir représenter l’évolution de plusieurs valeurs, par exemple
pour comparaison. Dans MatPlotLib, il suffit d’appeler plusieurs fois la fonction plt.plot(),
une fois pour chaque ligne, comme le montre cet exemple.

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


valeurs2 = [3, 8, 9, 2, 1, 2, 4, 7, 6, 6]
plt.plot(range(1,11), valeurs)
plt.plot(range(1,11), valeurs2)
plt.show()

Lorsque vous exécutez cet exemple, vous voyez deux lignes (Figure 10.2). Dans la version
papier du livre, les lignes sont représentées dans deux niveaux de gris différents, mais en
réalité, elles sont en couleurs.

Figure 10.2 : Tracé de deux lignes.

Stockage d’un diagramme dans un fichier


Du fait que les diagrammes sont insérés directement dans le calepin de Jupyter Notebook, il
devient possible de fournir des explications sous forme graphique et textuelle, ce qui permet
de produire de véritables rapports très lisibles. Lorsque vous avez besoin d’enregistrer un
diagramme, par exemple pour le réutiliser dans un autre document, il suffit d’utiliser la
fonction plt.savefig() comme dans cet exemple :

import matplotlib.pyplot as plt


%matplotlib auto

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


plt.plot(range(1,11), valeurs)
plt.ioff()
plt.savefig(‘MonDiagramme.png’, format=’png’)

Vous devez fournir deux paramètres au moins. Le premier est bien sûr le nom du fichier
destinataire, éventuellement précédé d’un chemin d’accès. Le second paramètre décide du
format du fichier. Dans l’exemple, nous choisissons le format très répandu PNG (Portable
Network Graphic). Plusieurs autres formats sont disponibles : PDF, PostScript (PS), Postscript
encapsulé (EPS) et SVG (Scalable Vector Graphics).
Vous aurez noté, à la deuxième ligne, que la fonction magique %matplotlib est dotée du
paramètre auto au lieu du paramètre inline des deux premiers exemples. Cette instruction
permet de choisir le moteur de rendu (d’affichage) en coulisses (backend). Dans cet exemple,
l’affichage n’est plus réalisé dans le calepin. Différentes options sont possibles pour le choix du
moteur de rendu, en fonction de la version de Python et de MatPlotLib en vigueur. Certains
programmeurs préfèrent le paramètre notebook à la place de inline. Pour utiliser Notebook,
il faut redémarrer le noyau, et le résultat n’est pas toujours conforme aux attentes. La liste des
différents moteurs disponibles peut être affichée au moyen de %matplotlib -l. De plus, vous
désactivez les interactions avec le tracé par un appel à plt.ioff().

Ajout des axes, des repères et de la grille


Il n’est pas toujours simple de savoir ce que signifient les données affichées si vous ne
fournissez pas une échelle ou au minimum de quoi faire des comparaisons. Vous rendez la
lecture plus efficace en ajoutant des axes, des repères et une grille. Tous les diagrammes n’en
ont pas besoin ; vous activerez ces différents éléments selon les besoins. Voyons comment les
ajouter à vos diagrammes.
Les prochains exemples tentent d’utiliser le mode de rendu %matplotlib notebook, ce qui
vous permet de comparer à %matplotlib inline. Le moteur de rendu graphique n’est pas le
même. Il est possible que cela vous oblige à redémarrer le noyau moyen de la commande
Kernel/Restart pour voir s’afficher les exemples qui suivent.
N. d. T. : En cas de souci, rebasculez en mode de rendu inline ou auto.

Ajout des axes


Les axes permettent de visualiser l’abscisse en x et l’ordonnée en y. En général, vous pouvez
laisser la librairie ajouter ces deux axes avec les valeurs implicites, mais parfois, vous aurez
besoin de les personnaliser. L’extrait suivant montre comment accéder à la configuration des
axes d’un tracé :

import matplotlib.pyplot as plt


%matplotlib notebook

valeurs = [0, 5, 8, 9, 2, 0, 3, 10, 4, 7]


ax = plt.axes()
plt.plot(range(1,11), valeurs)
plt.show()

Nous avons stocké l’objet incarnant les axes dans une variable ax, ce qui est préférable à la
manipulation directe, car le code sera plus simple. Par exemple, nous activons les axes par
défaut en appelant plt.axes(). Nous récupérons un identifiant de ces axes dans la variable
ax. Rappelons qu’un identifiant est une sorte de poignée de préhension permettant de
manipuler un élément du programme. En programmation, un tel identifiant correspond à un
pointeur qui contient une adresse mémoire (handle).

Configuration des axes


Souvent, les paramètres standard pour les axes ne conviendront pas, et vous voudrez par
exemple empêcher que la plus grande valeur vienne buter contre le haut du diagramme.
L’exemple suivant montre comment contrôler très finement les deux axes à partir du moment
où vous avez moyen d’y accéder par l’identifiant :

import matplotlib.pyplot as plt


%matplotlib notebook

valeurs = [0, 5, 8, 9, 2, 0, 3, 10, 4, 7]


ax = plt.axes()
ax.set_xlim([0, 11])
ax.set_ylim([-1, 11])
ax.set_xticks([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
ax.set_yticks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
plt.plot(range(1,11), valeurs)
plt.show()

Nous intervenons sur les limites des axes au moyen d’un appel à set_xlim() puis à
set_ylim(), c’est-à-dire sur la longueur de chaque axe. Nous contrôlons les repères le long de
chaque axe avec set_xticks() et set_yticks(). Vous avez une liberté totale quant aux
légendes de chaque repère sur l’axe. La Figure 10.3 montre le résultat de cet exemple.
Comparez-le avec celui de la Figure 10.2.
L’instruction %matlplotlib notebook donne un aspect final assez différent. Des boutons de
contrôle sous le diagramme permettent de faire défiler et de zoomer, passer d’une vue à une
autre et sauvegarder le diagramme sur disque. Au-dessus du diagramme, un bouton sur le bord
droit de la barre permet de quitter le mode interactif ou mode d’édition. Vos retouches sont
mémorisées et le diagramme se présentera tel que vous l’avez prévu. Vous sortez aussi du
mode interactif dès que vous demandez l’affichage d’un autre diagramme.
N. d. T. : En cas de souci avec le mode notebook, rebasculez en mode de rendu inline ou
auto.

Figure 10.3 : Tracé avec contrôle des axes.

Ajout d’une grille


La grille ou quadrillage permet de situer plus précisément chacun des points de mesure. Cela
vous permet de bien lire les coordonnées en x et en y, et mieux en comparer plusieurs. Le seul
problème est que cette grille surcharge l’affichage et empêche de voir aussi nettement les
tracés des lignes ou des courbes. Vous choisirez en fonction de la situation. L’exemple suivant
montre comment ajouter une grille au diagramme précédent :

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [0, 5, 8, 9, 2, 0, 3, 10, 4, 7]


ax = plt.axes()
ax.set_xlim([0, 11])
ax.set_ylim([-1, 11])
ax.set_xticks([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
ax.set_yticks([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
ax.grid()
plt.plot(range(1,11), valeurs)
plt.show()

Il suffit d’appeler la fonction grid(). Comme la plupart des autres fonctions de MatPlotLib,
vous pouvez lui fournir d’autres paramètres pour adapter la grille à vos convenances, par
exemple n’ajouter des lignes d’instance que sur l’axe x plutôt que sur les deux. La
Figure 10.4 montre le résultat du précédent exemple.

Figure 10.4 : Ajout d’une grille pour faciliter la lecture.

Contrôle de l’aspect des lignes


Il ne suffit pas de faire tracer des lignes pour bien véhiculer les informations à vos utilisateurs.
Souvent, vous devrez choisir des styles de tracé différents pour que les tracés se distinguent.
Pour mettre une courbe en exergue, vous utiliserez des couleurs ; vert pour quelque chose de
positif et rouge pour le contraire ou un danger (du moins en Occident). Découvrons les
possibilités de contrôle du style et de la couleur de tracé, ce qui permettra de communiquer
plus d’informations sans utiliser de texte.

DIAGRAMMES ET HANDICAP

Il est essentiel de penser aux personnes souffrant d’un handicap visuel, par exemple concernant la
discrimination des couleurs. Un daltonien ne pourra pas distinguer une ligne verte d’une ligne rouge.
Un malvoyant ne distinguera pas une ligne en pointillé d’une ligne tiretée. Cherchez donc à combiner
plusieurs modes pour aider à distinguer les tracés afin d’augmenter les chances d’être compris du
plus grand nombre.

Choix des styles de tracé


En choisissant un style qui soit assez différent pour chacune des séries de valeurs, vous rendez
votre diagramme beaucoup plus lisible, même si vous imprimez le résultat en niveaux de gris.
Pour mettre en valeur un tracé, vous changez seulement son style de tracé. Les différents
styles proposés par MatPlotLib sont montrés dans le tableau suivant.

Tableau 10.1 : Styles de tracé de MatPlotLib.

Paramètre Style
‘-’ Trait plein
‘--’ Tireté
‘-.’ Tiret-point
‘:’ Pointillé

Le style de tracé doit être le troisième paramètre de la fonction plot(). Il suffit de fournir la
chaîne de caractères contenant le symbole désiré, comme le montre l’exemple suivant.

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


valeurs2 = [3, 8, 9, 2, 1, 2, 4, 7, 6, 6]
plt.plot(range(1,11), valeurs, ‘--’)
plt.plot(range(1,11), valeurs2, ‘:’)
plt.show()

Dans l’exemple, la première série est tracée avec un style tireté et la seconde avec un style
pointillé (Figure 10.5).

Figure 10.5 : Styles de tracé pour distinguer les séries.

Contrôle des couleurs


En variant les couleurs de vos tracés, vous augmentez la lisibilité du diagramme. Cette
technique souffre en cas de reproduction sur une imprimante noir et blanc (sans compter le
problème du daltonisme), mais en général, ajouter la couleur rend la présentation plus
attrayante. Voici celles qui sont disponibles dans MatPlotLib.

Tableau 10.2 : Paramètres de couleur de MatPlotLib.

Paramètre Couleur
‘b’ Bleu
‘g’ Vert
‘r’ Rouge
‘c’ Cyan
‘m’ Magenta
‘y’ Jaune
‘k’ Noir
‘w’ Blanc

Comme pour les styles de tracé, vous fournissez votre valeur dans le troisième paramètre de la
fonction plot(). Dans notre exemple, il y aura une ligne rouge et une magenta. Le résultat
reste celui de la Figure 10.2, mais avec deux couleurs de tracé différentes. Dans la version
imprimée du livre, vous ne voyez que des niveaux de gris différents.

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


valeurs2 = [3, 8, 9, 2, 1, 2, 4, 7, 6, 6]
plt.plot(range(1,11), valeurs, ‘r’)
plt.plot(range(1,11), valeurs2, ‘m’)
plt.show()

Ajout de marqueurs
Les marqueurs sont des symboles spéciaux qui peuvent être associés à chaque point de
données d’un diagramme en lignes. Vous avez bien moins de souci à vous faire avec les
marqueurs qu’avec les lignes et les couleurs au niveau de l’accessibilité. Même lorsque le
marqueur n’est pas facile à voir, il reste en général facile à distinguer des autres marqueurs.
Le tableau suivant montre les marqueurs disponibles dans MatPlotLib.
Tableau 10.3 : Marqueurs de MatPlotLib.

Paramètre Type de marqueur


‘.’ Point
‘,’ Pixel
‘o’ Cercle
‘v’ Triangle 1 bas
‘^’ Triangle 1 haut
‘<’ Triangle 1 gauche
‘>’ Triangle 1 droite
‘1’ Triangle 2 bas
‘2’ Triangle 2 haut
‘3’ Triangle 2 gauche
‘4’ Triangle 2 droite
‘s’ Carré
‘p’ Pentagone
‘*’ Étoile
‘h’ Hexagone style 1
‘H’ Hexagone style 2
‘+’ Signe Plus
‘x’ Signe X
‘D’ Losange
‘d’ Diamant fin
‘¦’ Trait vertical
‘_’ Trait horizontal

Comme pour les tracés et les couleurs, votre paramètre de marqueur entre dans le troisième
paramètre de la fonction plot(). L’exemple suivant combine justement un style de ligne et un
de marqueur pour personnaliser chacun des deux tracés.

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


valeurs2 = [3, 8, 9, 2, 1, 2, 4, 7, 6, 6]
plt.plot(range(1,11), valeurs, ‘o--’)
plt.plot(range(1,11), valeurs2, ‘v:’)
plt.show()

La Figure 10.6 montre clairement que l’ajout de ces marqueurs combiné à la différence de
styles de trait rend le résultat beaucoup plus lisible, même sur une imprimante monochrome.

Figure 10.6 : Les marqueurs ajoutent encore à la lisibilité des valeurs individuelles.
Ajout de labels, d’annotations et de légendes
Pour rendre vos diagrammes encore plus accessibles, vous devez y ajouter des labels d’axes,
des annotations et des cartouches de légendes. Voici à quoi servent ces différents
compléments :
» Labels d’axes : permettent d’identifier un groupe ou une série de données, afin que le
lecteur sache exactement quel type d’information lui est proposé.
» Annotations : servent à fournir une information complémentaire au sujet des données.
Les annotations assurent la bonne compréhension de certains points du diagramme.
» Cartouche de légendes : fournit les noms des groupes ou série de données tracées en
indiquant en général, le type ou la couleur de trait utilisé.

Découvrons les compléments que MatPlotLib permet d’ajouter à votre diagramme. Tous les
diagrammes n’ont pas besoin de tous ces compléments, mais n’hésitez pas à ajouter ceux qui
semblent nécessaires pour transmettre le plus clairement possible le fruit de votre travail
d’analyse.

Ajout de labels d’axes


Ces labels sont indispensables pour que le lecteur sache à quoi correspondent les deux axes.
En plus du texte, vous pouvez ajouter une unité de mesure pour que le lecteur sache apprécier
les données en termes quantitatifs. Voici un exemple :

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


plt.xlabel(‘Entrées’)
plt.ylabel(‘Valeurs’)
plt.plot(range(1,11), valeurs)
plt.show()

L’appel à xlabel() documente l’axe x et celui à ylabel() documente l’autre (Figure 10.7).

Figure 10.7 : Ajout de légendes d’axes.

Ajout d’annotations
Une annotation sert à mettre l’accent sur un endroit en particulier dans le diagramme, par
exemple un point de données dont la valeur déborde de la plage prévue. L’exemple suivant
ajoute une annotation.

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


plt.annotate(xy=[4,9], s=’Valeur 4’)
plt.plot(range(1,11), valeurs)
plt.show()

Cette insertion correspond à l’appel à annotate(). Le premier paramètre de la fonction doit


stipuler les coordonnées du début de l’annotation, l’autre définit le texte au moyen de la
variable s. D’autres paramètres peuvent être ajoutés pour intervenir sur le format ou la
position exacte à l’écran dans le diagramme. Le résultat est visible en Figure 10.8.

Figure 10.8 : Ajout d’une annotation à un point de données.

Ajout d’un cartouche de légendes


Le cartouche de légendes permet de distinguer les différentes séries de données. Dans un
diagramme représentant les ventes de deux années successives, le cartouche va permettre de
savoir quel trait correspond à quelle année. Voici un exemple :

import matplotlib.pyplot as plt


%matplotlib inline

values = [1, 5, 8, 9, 2, 0, 3, 10, 4, 7]


values2 = [3, 8, 9, 2, 1, 2, 4, 7, 6, 6]
line1 = plt.plot(range(1,11), values)
line2 = plt.plot(range(1,11), values2)
plt.legend([‘Primo’, ‘Secundo’], loc=4)
plt.show()

Nous appelons la fonction legend() après avoir réalisé les deux tracés, et surtout pas avant
(alors que c’était le contraire pour d’autres fonctions de ce chapitre). Notez que vous devez
prévoir une variable pour recevoir l’identifiant de chacun des deux tracés. C’est ainsi que
line1 reçoit l’identifiant renvoyé par le premier appel à legend() et line2 reçoit celui du
second appel.
Par défaut, le cartouche est positionné dans le coin supérieur droit, ce qui n’est pas idéal dans
notre exemple. Grâce au paramètre loc, nous pouvons choisir une autre localisation, ici, le
coin inférieur droit correspondant à la valeur 4. Pour les autres emplacements, voyez la
documentation de la fonction
(https://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.legend). Le résultat
est visible dans la Figure 10.9.

Figure 10.9 : Ajout d’un cartouche de légende pour identifier les deux tracés.
Chapitre 11
Visualisation des données
DANS CE CHAPITRE :
» Choix du type de diagramme approprié

» Nuages et boîtes à moustaches

» Données temporelles et géographiques

» Graphes orientés et non orientés

L e Chapitre 10 nous a permis de découvrir les grands principes de l’utilisation de MatPlotLib.


Nous pouvons maintenant aller plus loin dans la découverte de cet outil en voyant comment
créer plusieurs des types de diagrammes les plus utilisés, même si nous ne présentons pas
tous ceux disponibles.

Nous allons bien sûr nous intéresser aux représentations les plus utiles en datalogie :
histogrammes et camemberts, mais également nuages et boîtes à moustaches qui permettent
de repérer plus aisément les motifs remarquables. Du fait que vous allez souvent travailler
avec des données temporelles ou géographiques, nous dédions une section à chacun de ces
deux sujets. Nous verrons enfin comment produire des graphes orientés et non orientés, très
prisés dans les analyses sociologiques des médias en ligne.
Le calepin des exemples de ce chapitre correspond au fichier PYDASC_11.

Choisir le bon diagramme


Le choix du type de diagramme va déterminer la facilité avec laquelle les gens vont pouvoir
exploiter les données. Par exemple, lorsqu’il s’agit de montrer les proportions de plusieurs
éléments par rapport à un tout, vous choisirez un diagramme sectoriel appelé camembert.
Lorsque vous avez besoin que les gens se fassent une idée de la manière dont plusieurs séries
de données se comparent, vous utiliserez un diagramme en barres. Votre objectif est de faire
interpréter les données de la façon que vous avez prévue suite à vos efforts d’analyse à partir
de différentes sources. Vous pouvez également choisir des tracés de lignes ou de courbes, déjà
vus dans le chapitre précédent. Découvrons quelques types de diagrammes à travers des
exemples.

Camemberts pour les proportions


Les camemberts permettent de montrer les proportions de différents éléments sur un tout. Le
cercle complet correspond à 100 %, et un pourcentage est attribué à chacun des constituants.
L’exemple suivant crée un camembert en utilisant plusieurs paramètres optionnels :

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [5, 8, 9, 10, 4, 7]


couleurs = [‘b’, ‘g’, ‘r’, ‘c’, ‘m’, ‘y’]
legaxe = [‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’]
explose = (0, 0.2, 0, 0, 0, 0)

plt.pie(valeurs, colors=couleurs, labels=legaxe,


explode=explose, autopct=’%1.1f%%’,
counterclock=False, shadow=True)
plt.title(‘Valeurs’)
plt.show()

Le paramètre indispensable correspond bien sûr aux valeurs, et vous pourriez produire un
camembert minimal en ne fournissant que ce paramètre.
Le paramètre couleurs permet de choisir la couleur de chaque tranche et le paramètre
legaxe d’identifier chacune d’elles. Vous aurez souvent besoin de faire ressortir une des
valeurs, ce que permet le paramètre explose avec sa liste de valeurs d’écartement. La
valeur 0 maintient la part collée aux autres.
Parmi les informations qui peuvent être associées à chaque part, la plus importante est le
pourcentage numérique, que fait afficher le paramètre autopct. Notez que vous devez lui
fournir une chaîne de formatage.
N.d.T. : Dans cette chaîne, les valeurs numériques indiquent le nombre minimal de chiffres
avant la virgule (le point décimal) et le nombre maximal après ce séparateur. La lettre f
signifie qu’il faut afficher les valeurs en tant que numériques à virgule flottante
(fractionnaires) et le redoublement du second signe pourcentage permet d’afficher ce signe au
lieu de laisser sa fonction habituelle de métacaractère marquant une position.
Parmi les autres paramètres, counterclock qui est un booléen permet de choisir de répartir
les parts en sens horaire ou antihoraire. Le paramètre shadow demande d’afficher une ombre
sous le camembert pour donner un effet de relief. D’autres paramètres sont décrits à l’adresse
https://matplotlib.org/api/pyplot_api.html.
Il est bien sûr souvent indispensable d’ajouter un titre, en utilisant la fonction title(). Le
résultat est visible en Figure 11.1.

Figure 11.1 : Camembert montrant les pourcentages des parties.

Diagrammes en barres pour comparer


Le diagramme en barres horizontales ou verticales permet facilement de comparer des valeurs
en les séparant visuellement, alors que les diagrammes en lignes montrent plutôt les
évolutions des séries. Vous disposez de toute une panoplie de méthodes pour mettre en
exergue certaines valeurs et apporter d’autres retouches. L’exemple suivant donne un exemple
simple de diagramme en barres verticales.

import matplotlib.pyplot as plt


%matplotlib inline

valeurs = [5, 8, 9, 10, 4, 7]


largeurs = [0.7, 0.8, 0.7, 0.7, 0.7, 0.7]
couleurs = [‘b’, ‘r’, ‘b’, ‘b’, ‘b’, ‘b’]
plt.bar(range(0, 6), valeurs, width=largeurs,
color=couleurs, align=’center’)

plt.show()

Vous devez au minimum fournir la série de coordonnées x et les hauteurs des barres. Notre
exemple utilise la fonction range() pour créer les coordonnées en x et la variable valeur
pour les hauteurs.
En guise de personnalisation, nous utilisons le paramètre de largeur width qui permet de
décider des largeurs de chaque barre. Dans l’exemple, la deuxième barre est rendue un peu
plus large pour la distinguer même dans une impression monochrome. Nous utilisons le
paramètre color pour modifier la couleur de cette barre qui apparaît en rouge, les autres
restant en bleu.
Comme les autres types de diagrammes, un certain nombre d’options sont possibles. Dans
l’exemple, nous utilisons le paramètre align pour centrer les données selon l’axe x au lieu de
les laisser alignées à gauche. Parmi les autres paramètres, citons hatch qui améliore le rendu
visuel. La Figure 11.2 montre l’affichage résultant.

Figure 11.2 : Un diagramme en barres pour comparer.

Ce chapitre ne montre que les grands principes de MatPlotLib pour créer des diagrammes.
Vous trouverez d’autres exemples sur le site de référence
(https://matplotlib.org/1.2.1/examples/index.html). Vous remarquerez quelques
exemples très sophistiqués, notamment ceux qui incorporent de l’animation. Avec un peu de
pratique, vous pourrez vous aussi les exploiter pour représenter vos données.

Histogrammes pour les distributions


Un histogramme présente des catégories de données, auparavant réparties en plusieurs
groupes, chaque groupe contenant un sous-ensemble de la plage de données complètes.
L’histogramme affiche le nombre d’éléments dans chaque groupe, ce qui permet de voir la
distribution des données entre les différents groupes. Dans la majorité des cas, il en résulte
une sorte de courbe en forme de cloche. L’exemple suivant crée un histogramme à partir de
données pseudo-aléatoires :

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

x = 20 * np.random.randn(10000)

plt.hist(x, 25, range=(-50, 50), histtype=’stepfilled’,


align=’mid’, color=’g’, label=’Données de test’)
plt.legend()
plt.title(‘Histogramme avec remplissage’)
plt.show()

Les données d’entrée sont choisies au hasard et la distribution doit produire une courbe de
type cloche. Vous devez fournir la série de valeurs x. Le deuxième paramètre de la fonction
hist() spécifie le nombre de groupes (bins). La valeur implicite est égale à 10. Le paramètre
range limite la représentation aux valeurs principales, en ignorant les valeurs aberrantes.
Plusieurs types d’histogrammes sont disponibles. La valeur par défaut crée un histogramme de
type diagramme en barres simples. Vous pouvez également créer des barres empilées, étagées
ou étagées et remplies (type de l’exemple). Vous pouvez enfin choisir l’orientation (verticale
par défaut).
Comme la plupart des autres diagrammes du chapitre, vous disposez de certaines options. Le
paramètre align détermine la position de chaque barre par rapport à la ligne de base. Le
paramètre color sert à choisir les couleurs des barres. Enfin, le paramètre label ne sera
visible que si vous demandez également le cartouche de légendes (comme dans l’exemple de la
Figure 11.3).
Figure 11.3 : Histogramme visualisant la distribution des valeurs.

Le diagramme sera légèrement différent à chaque exécution, puisqu’il part de valeurs choisies
de façon pseudo-aléatoire.

Boîtes à moustaches pour les groupes


Une boîte à moustaches permet de bien visualiser des groupes de valeurs en les répartissant
en quartiles (chaque groupe est divisé en quatre parties égales). Les moustaches d’une telle
boîte (whiskers) repèrent les données en dehors des quartiles supérieur et inférieur sous forme
de traits. L’espace utilisé dans la boîte indique les déviations et dispersions dans les données.
L’exemple suivant crée une boîte à moustaches à partir de données aléatoires :

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

spread = 100 * np.random.rand(100)


center = np.ones(50) * 50
flier_high = 100 * np.random.rand(10) + 100
flier_low = -100 * np.random.rand(10)
data = np.concatenate((spread, center,
flier_high, flier_low))

plt.boxplot(data, sym=’gx’, widths=.75, notch=True)


plt.show()

Pour cet exemple, il nous faut préparer un jeu de données d’entrée en combinant plusieurs
techniques de production de valeurs. Voici comment nous utilisons ces techniques :
» spread : contient un jeu de valeurs aléatoires entre 0 et 100 ;
» center : génère 50 valeurs directement au centre de la plage de 50 ;
» flier_high : simule des valeurs aberrantes entre 100 et 200 ;
» flier_low: simule des valeurs aberrantes entre 0 et 100.

Ces différents lots de valeurs sont réunis en un seul jeu de données au moyen de la fonction
concatenate(). Ce jeu est généré aléatoirement, mais il possède des caractéristiques
prédéfinies, et notamment un grand nombre de valeurs vers le milieu. Il variera donc d’une
exécution à l’autre, mais conviendra pour notre exemple.
L’appel à la fonction de tracé boxplot() n’a besoin que du paramètre data pour les valeurs.
Les autres paramètres ont des valeurs par défaut. Dans l’exemple, les valeurs aberrantes sont
des X verts grâce au paramètre sym. Le paramètre width contrôle la taille de la boîte (que
nous avons volontairement énormément étirée en largeur pour la rendre plus visible). Enfin, le
paramètre notch permet d’ajouter une encoche sur les flancs de la boîte (ce paramètre vaut
false par défaut). La Figure 11.4 montre un résultat possible.
Figure 11.4 : Boîte à moustaches visualisant des groupes de valeurs.

Dans la boîte, la ligne rouge au milieu correspond aux valeurs médianes. Les deux lignes
horizontales séparées de la boîte par des traits indiquent les limites supérieure et inférieure
pour les quatre quartiles. Les valeurs aberrantes sont visibles au-dessus et en dessous sous
forme de signes X.

Nuages de points pour les motifs


Les diagrammes en nuage de points montrent des grappes (clusters) de données plutôt que des
tendances (comme les diagrammes en lignes), ou des valeurs distinctes (comme les
histogrammes). L’objectif principal d’un nuage est de permettre de repérer des motifs
remarquables. L’exemple suivant crée un diagramme en nuage à partir de données aléatoires :

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

x1 = 5 * np.random.rand(40)
x2 = 5 * np.random.rand(40) + 25
x3 = 25 * np.random.rand(20)
x = np.concatenate((x1, x2, x3))

y1 = 5 * np.random.rand(40)
y2 = 5 * np.random.rand(40) + 25
y3 = 25 * np.random.rand(20)
y = np.concatenate((y1, y2, y3))

plt.scatter(x, y, s=[100], marker=’^’, c=’m’)


plt.show()

L’exemple commence par générer des coordonnées x, y aléatoires. Pour chaque coordonnée x,
il faut disposer d’une coordonnée y. Vous pouvez tout à fait produire un tel diagramme
uniquement avec ces jeux de coordonnées.
Pour personnaliser le diagramme, servez-vous par exemple du paramètre s qui décide de la
taille de chaque point de données. Le paramètre marker contrôle la forme du point et le
paramètre c définit les couleurs de tous les points (mais vous pouvez aussi choisir les couleurs
individuellement). Le résultat est visible dans la Figure 11.5.
Figure 11.5 : Diagramme en nuage montrant des groupes de données et donc des motifs.

Nuages de points avancés


Les diagrammes en nuage de points sont donc indispensables en datalogie dès qu’il s’agit de
rendre visibles des motifs remarquables dans les données. Vous visualisez ainsi aisément
l’appartenance des données aux différents groupes ainsi que le chevauchement des valeurs qui
sont à l’écart de la plage attendue. Vous devez savoir visualiser de telles relations dans vos
données, et la librairie MatPlotLib peut grandement vous y aider. Découvrons ces techniques
évoluées concernant les diagrammes en nuage en reprenant les données déjà utilisées dans ce
chapitre.

Visualisation de groupes
Dans un diagramme en nuage, la troisième série de données (le troisième axe) est incarnée par
différentes couleurs. Grâce à ces couleurs, vous allez pouvoir mettre en valeur certains
groupes pour les distinguer des autres. L’exemple qui suit montre comment profiter de cette
possibilité dans un nuage de points :

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

x1 = 5 * np.random.rand(50)
x2 = 5 * np.random.rand(50) + 25
x3 = 30 * np.random.rand(25)
x = np.concatenate((x1, x2, x3))

y1 = 5 * np.random.rand(50)
y2 = 5 * np.random.rand(50) + 25
y3 = 30 * np.random.rand(25)
y = np.concatenate((y1, y2, y3))

tabcoul = [‘b’] * 50 + [‘g’] * 50 + [‘r’] * 25

plt.scatter(x, y, s=[50], marker=’D’, c=tabcoul)


plt.show()

L’exemple ressemble beaucoup au précédent, hormis des valeurs un peu différentes, et surtout
une nouvelle ligne pour définir un tableau de couleurs. Dans la version imprimée du livre, vous
distinguerez dans le résultat les valeurs par des niveaux de gris (Figure 11.6). Le premier
groupe est en bleu, le second en vert et le dernier en rouge.
Figure 11.6 : Tableau de couleurs pour distinguer les groupes.

Visualisation de corrélations
Vous aurez parfois besoin de visualiser une tendance générale, d’après les données du nuage.
En effet, même si les différents groupes sont faciles à distinguer, cela ne permet pas toujours
d’en déduire une direction, une orientation globale. Mais vous pouvez ajouter une ligne de
tendance comme le fait l’exemple suivant. Nous avons modifié le jeu de données d’entrée pour
que les groupes soient moins faciles à distinguer, justifiant ainsi l’ajout de la ligne de tendance
(Figure 11.7).

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.pylab as plb
%matplotlib inline

x1 = 15 * np.random.rand(50)
x2 = 15 * np.random.rand(50) + 15
x3 = 30 * np.random.rand(25)
x = np.concatenate((x1, x2, x3))

y1 = 15 * np.random.rand(50)
y2 = 15 * np.random.rand(50) + 15
y3 = 30 * np.random.rand(25)
y = np.concatenate((y1, y2, y3))

tabcoul = [‘b’] * 50 + [‘g’] * 50 + [‘r’] * 25


plt.scatter(x, y, s=[90], marker=’*’, c=tabcoul)
z = np.polyfit(x, y, 1)
p = np.poly1d(z)
plb.plot(x, p(x), ‘m-’)

plt.show()

Le code source reste très proche des deux précédents, sauf que les données empêchent de
bien distinguer les groupes. Pour ajouter la ligne, nous appelons la fonction polyfit() en lui
fournissant les données. La fonction renvoie un vecteur de coefficients p destiné à réduire
l’erreur des moindres carrés. (La régression par moindres carrés est une méthode permettant
de trouver une ligne qui fait la somme des relations entre deux variables x et y, au moins dans
le domaine de la variable explicative x. Le troisième paramètre de polyfit() détermine le
degré d’adaptation polynomiale.
Le vecteur produit par polyfit() est utilisé en entrée de polyid() pour calculer les points de
données réels selon l’axe y. Il ne reste qu’à créer la ligne de tendance avec un appel à plot()
(Figure 11.7).
Figure 11.7 : Nuage de points avec une ligne de tendance générale.

Tracé de séries temporelles


Tout change sans cesse dans le monde. Lorsque vous visualisez des données, vous montrez un
instantané, c’est-à-dire la valeur des données à un moment particulier. Ce genre de point de
vue est habituel et très utile, mais parfois, vous devez montrer comment les données évoluent
au cours du temps. C’est le seul moyen permettant de trouver les causes de ces changements.
Découvrons donc comment travailler avec des données selon un axe temporel.

Représentation selon un axe temporel


Vous aurez souvent besoin de présenter l’évolution des données au cours du temps. En général,
vous disposerez dans le jeu de données d’entrée de mesures périodiques, par unité de temps,
pour une ou plusieurs caractéristiques, décrivant leur état à ce moment précis. L’exemple
suivant montre les volumes de ventes sur une série de jours pour un article en particulier. Les
quantités sont des valeurs entières.

import pandas as pd
import matplotlib.pyplot as plt
import datetime as dt
%matplotlib inline

date_deb = dt.datetime(2020, 7, 29)


date_fin = dt.datetime(2020, 8, 7)
date_plage = pd.date_range(date_deb, date_fin)
ventes = (np.random.rand(len(date_plage)) * 50).astype(int)
df = pd.DataFrame(ventes, index=date_plage,
columns=[‘VENTES’])

df.loc[‘Jul 30 2020’:’Aug 05 2020’].plot()


plt.ylim(0, 50)
plt.xlabel(‘Date de vente’)
plt.ylabel(‘Volume de vente’)
plt.title(‘Durée observation’)
plt.show()

Nous commençons par générer notre source de données à la volée vers une structure
DataFrame (mais n’importe quelle autre source peut bien sûr convenir). Vous remarquez que
nous créons une plage de dates dans la variable date_plage pour mémoriser les bornes
inférieure et supérieure des dates, ce qui permettra un traitement plus efficace dans une
boucle for. L’exemple crée le cadre de données dans la variable df.
Figure 11.8 : Diagramme d’évolution des données dans le temps.

Visualisation d’une tendance temporelle


Comme avec les autres styles de diagrammes, il n’est parfois pas simple de deviner la direction
générale vers laquelle tendent les données. L’exemple suivant est une variation du précédent
en ajoutant une ligne de tendance :

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import datetime as dt
%matplotlib inline

date_deb = dt.datetime(2020, 7, 29)


date_fin = dt.datetime(2020, 8, 7)
date_plage = pd.date_range(date_deb, date_fin)
ventes = (np.random.rand(len(date_plage)) * 50).astype(int)
df = pd.DataFrame(ventes, index=date_plage,
columns=[‘Ventes’])

lr_coef = np.polyfit(range(0, len(df)), df[‘Ventes’], 1)


lr_func = np.poly1d(lr_coef)
tendance = lr_func(range(0, len(df)))
df[‘Tendance’] = tendance
df.loc[‘Jul 30 2020’:’Aug 05 2020’].plot()

plt.xlabel(‘Date ventes’)
plt.ylabel(‘Volume ventes’)
plt.title(‘Jours’)
plt.legend([‘Ventes’, ‘Tendance’])
plt.show()

La section précédente qui abordait les corrélations montrait la technique généralement utilisée
pour ajouter une tendance. Dans certains cas, le rendu visuel n’est pas le plus lisible. Le
présent exemple adopte une autre approche : il ajoute directement la ligne de tendance dans la
structure DataFrame. Si vous demandez l’affichage de df après la ligne df[‘trend’] =
tendance, vous verrez apparaître une série de données ressemblant à la suivante :

Ventes Tendance
2020-07-29 37 9.454545
2020-07-30 8 12.242424
2020-07-31 7 15.030303
2020-08-01 0 17.818182
2020-08-02 3 20.606061
2020-08-03 24 23.393939
2020-08-04 40 26.181818
2020-08-05 19 28.969697
2020-08-06 45 31.757576
2020-08-07 37 34.545455
Avec cette approche, les tracés deviennent plus simples. Vous n’avez à appeler plot() qu’une
fois et n’avez plus besoin de recourir à la partie pylab de MatPlotLib (revoyez si nécessaire la
partie décrivant les corrélations). Le code source est plus lisible et le résultat risque moins
d’entraîner les désagréments que l’on voit fréquemment dans les diagrammes publiés sur le
Web.
Dans le premier exemple de corrélations, l’appel à plot() faisait générer automatiquement
une légende, mais MatPlotLib n’ajoute pas automatiquement de ligne de tendance. Voilà
pourquoi il faut créer une nouvelle légende pour le tracé. La Figure 11.9 montre le résultat
produit à partir de données générées aléatoirement.

Figure 11.9 : Ajout d’une ligne de tendance à un diagramme temporel.

Visualisation de données géographiques


Il est souvent indispensable de pouvoir situer les données dans l’espace physique. Lorsque
vous avez par exemple besoin de montrer dans quelle région il y aura pénurie d’eau afin de
mettre en place des cellules de crise, il est nécessaire de combiner les données à des repères
géographiques. Il en va de même pour faire des prévisions de vente. Vous partez de données
existantes pour décider des implantations de vos nouveaux magasins. Pour combiner ces
données à des informations géographiques, nous allons utiliser l’application Basemap.
Vous devez absolument quitter totalement l’environnement de Jupyter Notebook avant de
réaliser l’installation qui suit pour ne pas voir l’outil conda se plaindre qu’un fichier n’est pas
accessible. Pour quitter cet environnement, sauvegardez si nécessaire les calepins en cours et
quittez les sessions dans chaque fenêtre ouverte. Activez ensuite la fenêtre du terminal du
serveur Notebook et frappez la combinaison d’arrêt (Ctrl+C). Patientez quelques secondes
pour laisser le serveur bien refermer tous les fichiers.

Ajout d’un environnement dans Notebook


Certains des paquetages ou applications complémentaires que vous allez installer peuvent
avoir tendance à modifier l’environnement d’exécution de Jupyter Notebook, à cause de
paquetages qui fonctionnent mal avec la configuration principale. Il en résulte que des
problèmes d’exécution peuvent survenir avec du code qui fonctionnait correctement
auparavant. En général, cela se limite à des messages d’avertissement, tels que ceux
d’obsolescence décrits dans une section ultérieure de ce chapitre. Mais il y a plus grave.
Parfois, la compatibilité peut avoir un effet sur les données générées. Il peut s’agir d’un
nouveau paquetage qui a modifié son algorithme ou qui interagit autrement avec le code du
noyau. Lorsque vous préférez maintenir propre la configuration actuelle, mais voulez ajouter
un paquetage tel que Basemap, la solution consiste à créer un environnement transitoire. Vous
conservez ainsi intacte la configuration de base, tout en permettant au nouveau paquetage de
fonctionner dans un environnement qui lui convient mieux. Voici comment mettre en place un
tel environnement pour Basemap dont nous avons besoin un peu plus loin :
1. Ouvrez une fenêtre de terminal Anaconda Prompt.
Vous remarquez que le texte d’invite rappel l’emplacement actuel dans les répertoires, mais
également le nom de l’environnement actif qui est au départ (base). C’est cet
environnement que vous ne voulez pas altérer.
2. Saisissez la commande suivante et validez par la touche Entrée.

conda create -n Basemap python=3 anaconda=5.2.0

Vous demandez ainsi la création d’un nouvel environnement Basemap utilisant Python en
version 3 et Anaconda en version 5.2. (Il est possible que ce soit les mêmes numéros de
version que votre configuration de base.)
3. Si vous êtes sous MacOS X ou sous Linux, saisissez la commande suivante :

source activate Basemap

Sous Windows, saisissez directement activate Basemap.


Dès que vous validez par Entrée, vous basculez dans le nouvel environnement, ce que
confirme l’invite qui indique dorénavant (Basemap).
4. Vous pouvez maintenant installer votre exemplaire de Basemap en suivant les
instructions fournies dans la section suivante.
5. Il ne reste plus qu’à redémarrer Jupyter Notebook.
L’application Notebook redémarre, mais dans le nouvel environnement Basemap. Le
comportement reste le même, la seule différence étant que cet environnement est
spécifique.
Vous utiliserez cette technique pour tout paquetage complémentaire un peu spécial que vous
désirez installer. Ce changement d’environnement convient pour un paquetage que vous
n’utiliserez pas en permanence. Ici, nous n’utilisons Basemap que pour l’exemple qui suit, et
un environnement transitoire est approprié.
Lorsque vous aurez terminé d’utiliser l’environnement Basemap, n’oubliez pas d’utiliser la
commande deactivate dans la fenêtre texte en validant par Entrée. Vous devez voir l’invite
indiquer l’environnement (base).

Obtention du toolkit Basemap


Parmi les nombreux paquetages contenant une librairie de support pour des données
géographiques, la plus simple à installer et à utiliser est Basemap Toolkit. Rendez-vous sur le
site correspondant pour en télécharger le fichier
(https://matplotlib.org/basemap/users/intro.html). Rappelons que vous devez avoir
totalement quitté l’application Jupyter Notebook, y compris la fenêtre du serveur, afin d’éviter
toute erreur d’accès à un fichier. La technique la plus simple pour l’installation consiste à vous
servir de l’utilitaire conda depuis la fenêtre Anaconda Prompt en entrant les trois commandes
suivantes :

conda install -c conda-forge basemap=1.1.0


conda install -c conda-forge basemap-data-hires
conda install -c conda-forge proj4=5.2.0

Le site mentionné donne des informations au sujet du paquetage ; à la différence de nombreux


autres sites, vous y trouverez des instructions aussi bien pour MacOS que pour Windows ou
Linux. Ceux qui travaillent sous Windows peuvent préférer recourir à l’installateur.
N.d.T. : Les anglophones peuvent consulter la vidéo d’explication à l’adresse
http://nbviewer.ipython.org/github/mqlaql/geospatial-
data/blob/master/Geospatial-Data-with-Python.ipynb.

Une fois que le kit est installé, les programmes qui veulent l’utiliser doivent absolument
commencer par les quatre directives suivantes :

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
%matplotlib inline
Gestion des librairies dépréciées
Le langage Python possède, parmi ses nombreux avantages, celui d’être entouré d’un très
grand nombre de paquetages complémentaires. Cependant, tous ces paquetages ne sont pas
mis à jour suffisamment vite pour éviter de voir apparaître des fonctions dépréciées dans
d’autres paquetages qui en dépendent. Une fonction dépréciée est une fonction qui existe dans
le paquetage concerné, mais que les auteurs du paquetage se préparent à supprimer ou à
remplacer. C’est la raison pour laquelle vous voyez des messages vous avertissant de cette
obsolescence. Ces messages n’empêchent pas le code d’être exécuté, mais les utilisateurs de
votre programme peuvent s’alarmer. Personne n’aime voir apparaître un message d’erreur au
milieu des résultats, d’autant plus que l’application Notebook affiche les messages en rouge
clair par défaut.
Il est hélas possible que le paquetage Basemap que vous avez installé soit à l’origine d’une
fonction dépréciée et donc d’un message. Cette section vise à corriger cette situation. Pour en
savoir plus au sujet des problèmes éventuels, vous pouvez consulter la page
https://github.com/matplotlib/basemap/issues/382. Voici l’allure générale d’un tel
message :

C:\Users\Luca\Anaconda3\lib\site-packages\mpl_toolkits
\basemap\__init__.py:1708: MatplotlibDeprecationWarning:
The axesPatch function was deprecated in version 2.1.
Use Axes.patch instead.
limb = ax.axesPatch
C:\Users\Luca\Anaconda3\lib\site-packages\mpl_toolkits
\basemap\__init__.py:1711: MatplotlibDeprecationWarning:
The axesPatch function was deprecated in version 2.1. Use
Axes.patch instead.
if limb is not ax.axesPatch:

Ce texte peut sembler terrifiant, mais il désigne simplement deux problèmes. Le premier
concerne la librairie MapPlotLib et notamment l’appel à axesPatch(). Les messages indiquent
en outre que cet appel a été déprécié depuis la version 2.1. Saisissez donc les deux commandes
suivantes pour vérifier la version de MatPlotLib :

import matplotlib
print(matplotlib.__version__)

Si vous avez bien installé Anaconda comme indiqué dans le Chapitre 3, vous devriez avoir au
minimum une version 2.2.2 de MatPlotLib. Il s’agit donc de revenir en arrière volontairement
sur la version de MatPlotLib en utilisant la commande suivante dans la fenêtre Anaconda
Prompt :

conda install -c conda-forge matplotlib=2.0.2

Bien sûr, ce rétrogradage peut créer des problèmes dans un autre code source qui utilise une
des nouveautés de MatPlotLib. La solution n’est pas idéale, mais elle peut vous sauver si vous
utilisez intensivement Basemap dans votre projet.
Une bonne solution consiste à simplement admettre qu’il y a un souci en documentant cela
dans le code source. Vous serez ainsi prêt à réagir au problème lors de la prochaine mise à jour
d’un paquetage. Il suffit d’ajouter les deux lignes de code source suivantes :

import warnings
warnings.filterwarnings(“ignore”)

L’appel à la fonction filterwarnings() réalise l’action demandée qui consiste ici à ignorer les
messages d’avertissement. Pour annuler ce masquage, vous utilisez la fonction
resetwarnings(). L’attribut nommé module de cette fonction est le même que celui qui est la
source des problèmes. Vous pouvez définir un masque plus vaste avec l’attribut category.
Dans notre exemple, le masque est restreint à un seul module.

Géographie avec Basemap


Si nous avons installé une librairie géographique, c’est pour nous en servir. L’exemple suivant
affiche une carte puis positionne des pointeurs à certains endroits :
austin = (-97.75, 30.25)
hawaii = (-157.8, 21.3)
washington = (-77.01, 38.90)
chicago = (-87.68, 41.83)

losangeles = (-118.25, 34.05)

m = Basemap(projection=’merc’,llcrnrlat=10,urcrnrlat=50,
llcrnrlon=-160,urcrnrlon=-60)

m.drawcoastlines()
m.fillcontinents(color=’lightgray’,lake_color=’lightblue’)
m.drawparallels(np.arange(-90.,91.,30.))
m.drawmeridians(np.arange(-180.,181.,60.))
m.drawmapboundary(fill_color=’aqua’)

m.drawcountries()

x, y = m(*zip(*[hawaii, austin, washington,


chicago, losangeles]))
m.plot(x, y, marker=’o’, markersize=6,
markerfacecolor=’red’, linewidth=0)

plt.title(“Projection de Mercator”)
plt.show()

Si nécessaire, ajoutez en début de ce code source les quatre lignes de test quelques pages en
arrière (trois directives d’import et une directive magique %matplotlib).

L’exemple commence par définir les coordonnées de cinq villes puis crée le fond de carte. Le
paramètre projection définit l’aspect général, les quatre paramètres llcrnrlat, urcrnrlat,
llcrnrlon et urcrnrlon règlent les bords de la carte. D’autres paramètres sont possibles en
option.
Les actions suivantes servent à personnaliser la carte. La fonction drawcoastlines() rend les
lignes côtières mieux visibles. Pour mieux distinguer la terre ferme de l’eau, vous appelez
fillcontinents() en choisissant vos couleurs. La fonction drawcountries() permet de
montrer les limites des états, ce dont nous avons besoin ici. Vous disposez à partir de ce
moment d’une carte prête à recevoir vos données.
Dans l’exemple, nous créons les coordonnées en x et en y à partir de celles définies pour les
cinq villes un peu plus haut. Nous affichons ensuite les positions sur la carte dans une couleur
différente. Il ne reste plus ensuite qu’à afficher le résultat (Figure 11.10).

Figure 11.10 : Insertion d’un jeu de données sur un fond de carte géographique.

Graphes orientés et non orientés


Au sens strict, un graphe montre les connexions entre des points de données qui sont des
nœuds. Dans un graphe, chaque nœud n’est pas nécessairement relié à tous les autres.
Prenons le cas d’un plan du métro. Chaque station est connectée à d’autres, mais aucune
station n’est connectée à toutes. Les graphes sont très utilisés en sociologie et dans l’analyse
des médias. En effet, ce domaine suppose de décrire et d’analyser des réseaux de relations, par
exemple les amis ou les connaissances professionnelles, en exploitant les données de sites tels
que Facebook, Twitter ou LinkedIn.
Il existe deux grandes catégories de graphes : les graphes non orientés, dans lesquels les
nœuds sont reliés par des lignes simples, et les graphes orientés, dans lesquels les lignes
comportent une flèche (ou deux) pour orienter les liens. Un graphe schématisant un système
hydraulique serait orienté, car l’eau ne coule que dans un sens. Découvrons comment réaliser
un graphe de chacun de ces deux types.

Création d’un graphe non orienté


Les graphes non orientés sont les plus simples puisqu’ils ne font qu’établir des liens entre des
nœuds. Il n’y a pas de sens dans les relations. Nous pouvons ainsi représenter graphiquement
les connexions entre différentes pages Web. Voici un exemple :

import networkx as nx
import matplotlib.pyplot as plt
%matplotlib inline

G = nx.Graph()
H = nx.Graph()
G.add_node(1)
G.add_nodes_from([2, 3])
G.add_nodes_from(range(4, 7))
H.add_node(7)
G.add_nodes_from(H)

G.add_edge(1, 2)
G.add_edge(1, 1)
G.add_edges_from([(2,3), (3,6), (4,6), (5,6)])
H.add_edges_from([(4,7), (5,7), (6,7)])
G.add_edges_from(H.edges())

nx.draw_networkx(G)
plt.show()

Dans le Chapitre 8, nous avions utilisé la librairie NetworkX pour construire un exemple. Ici,
nous utilisons plusieurs techniques. Nous commençons par importer cette librairie NetworkX
puis nous appelons le constructeur nommé Graph() qui attend plusieurs paramètres d’entrée
qui vont servir d’attributs. Notez que vous pouvez créer un graphe tout à fait exploitable sans
mentionner aucun de ces attributs (c’est d’ailleurs ce que nous faisons).
Pour ajouter un nœud, nous appelons add_node() en indiquant le numéro de nœud. Rien ne
vous empêche d’indiquer une liste, un dictionnaire ou une plage de nœuds avec range(), en
utilisant add_nodes_from(). Vous pouvez même importer des nœuds depuis un autre graphe.
Dans l’exemple, nous utilisons des nombres pour les nœuds, mais vous pouvez tout à fait
indiquer des lettres, des chaînes de caractères ou même des dates. En revanche, vous ne
pouvez pas utiliser de valeurs booléennes pour un nœud.

Au départ, il n’y a pas de lien autour d’un nouveau nœud. Pour en ajouter, vous utilisez la
fonction add_edge() en précisant les numéros des nœuds à relier. Comme pour les nœuds,
vous pouvez en ajouter plusieurs en un geste, au moyen de la fonction add_edges_from(). Vous
spécifiez alors une liste, un dictionnaire ou un autre graphe. Le résultat de l’exemple est
montré en Figure 11.11. (Ce résultat varie d’une exécution à l’autre, mais les connexions sont
les mêmes.)
Figure 11.11 : Graphe non orienté avec liens.

Création d’un graphe orienté


Un graphe orienté permet d’informer sur les sens de circulation d’un nœud à un autre. Vous en
aurez besoin pour montrer par exemple un trajet d’un point à un autre. Le nœud de départ et
celui d’arrivée sont marqués en conséquence, et les nœuds intermédiaires sont légendés.
N’hésitez pas à rendre vos graphes attrayants en les enrichissant avec différentes
informations : légendes, variation de couleurs des nœuds, et autres mesures pour faciliter la
compréhension du graphe. Vous pouvez bien sûr contrôler l’épaisseur de tracé des liens et
marquer un chemin entre des nœuds que vous considérez comme optimal. Dans l’exemple
suivant, nous exploitons plusieurs des options permettant de rendre un graphe orienté plus
lisible et plus intéressant, mais ce ne sont pas toutes les options disponibles.

import networkx as nx
import matplotlib.pyplot as plt
%matplotlib inline

G = nx.DiGraph()
G.add_node(1)
G.add_nodes_from([2, 3])
G.add_nodes_from(range(4, 6))
G.add_path([6, 7, 8])

G.add_edge(1, 2)
G.add_edges_from([(1,4), (4,5), (2,3), (3,6), (5,6)])

couleurs = [‘r’, ‘g’, ‘g’, ‘g’, ‘g’, ‘m’, ‘m’, ‘r’]


labels = {1:’Début’, 2:’2’, 3:’3’, 4:’4’,
5:’5’, 6:’6’, 7:’7’, 8:’Fin’}
tailles = [800, 300, 300, 300, 300, 600, 300, 800]

nx.draw_networkx(G, node_color=couleurs, node_shape=’D’,


with_labels=True, labels=labels,
node_size=tailles)
plt.show()

Nous commençons par créer un graphe orienté par un appel au constructeur DiGraph(). Notez
au passage que le paquetage NetworkX sait également gérer les types MultiGraph() et
MultiDiGraph(). Tous les types sont présentés dans la page https://mriduls-
networkx.readthedocs.io/en/latest/_modules/index.html.
L’ajout des nœuds se réalise de la même façon que pour un graphe non orienté. Pour en ajouter
un seul, vous utilisez add_node() et pour en ajouter plusieurs, add_nodes_from(). Pour
ajouter des nœuds et des liens en même temps, vous utilisez add_path(). Notez que l’ordre
des nœuds est essentiel. La circulation d’un nœud à l’autre va de gauche à droite dans la liste
fournie en paramètre.
L’ajout des liens se fait, comme dans un graphe non orienté, avec add_edge() pour un seul, et
add_edges_from() pour en ajouter plusieurs. L’ordre dans lequel les numéros des nœuds sont
fournis est critique. Le flux va de gauche à droite dans chaque paire de nœuds.
L’exemple ajoute des couleurs particulières pour les nœuds, des labels et utilise une seule
forme de point (avec contrôle de leur taille). La création proprement dite passe par un appel à
draw_networkx(). Notez qu’il faut donner la valeur True à with_labels pour que les légendes
du paramètre labels soient visibles. Le résultat est donné dans la Figure 11.12.

Figure 11.12 : Graphe orienté avec flèches de liens.


PARTIE 4
Transformation des données

DANS CETTE PARTIE :


» Installation et utilisation de plusieurs paquetages de datalogie
» Lancement d’une analyse de données
» Réduction des dimensions d’un jeu de données
» Regroupement des données de plusieurs jeux
» Amélioration de la qualité par suppression des aberrants
Chapitre 12
Renforcement des capacités de Python
DANS CE CHAPITRE :
» Utilisation des classes de Scikit-learn

» Matrices peu denses et technique de hachage

» Tests de performance et d’empreinte mémoire

» Accélération par les algorithmes multicœurs

L es chapitres précédents nous ont permis d’apprendre les techniques de base pour charger
les données, puis les manipuler en langage Python. Nous pouvons maintenant nous
intéresser à des outils plus complexes pour manipuler les données et faire de l’apprentissage
machine. Le but final de la plupart des projets de datalogie est, rappelons-le, la construction
d’un outil capable automatiquement de résumer, de prédire et de recommander des décisions
par simple analyse des données.
Avant d’en arriver là, il reste encore à retravailler les données en y appliquant des
transformations encore plus radicales. Nous entrons là dans le domaine de la reformulation
des données ; il s’agit de transformations sophistiquées suivies d’une analyse visuelle et
statistique, suivie à son tour d’autres transformations. Nous allons voir comment exploiter
d’énormes flux de texte, nous allons découvrir les caractéristiques fondamentales d’un jeu de
données, optimiser la vitesse des expériences, compresser les données et créer de nouvelles
caractéristiques de synthèse, de nouveaux groupes et classifications et enfin détecter des cas
inattendus ou exceptionnels qui pourraient créer du tort à la bonne conclusion de votre projet.
À partir de maintenant, nous allons utiliser beaucoup plus le paquetage nommé Scikit-learn (la
documentation complète est à l’adresse
https://scikitlearn.org/stable/documentation.html). Ce paquetage propose un
référentiel unique qui réunit quasiment tous les outils dont un datalogue a besoin. Nous
verrons dans ce chapitre les caractéristiques essentielles de Scikit-learn, qui est structuré en
modules, classes et fonctions. Nous verrons également quelques techniques pour gagner du
temps dans Python en améliorant ses performances. Elles se basent sur des données
volumineuses non structurées et utilisent des calculs très gourmands en puissance.
Le fichier calepin contenant le code source des exemples de ce chapitre porte le nom
PYDASC_12.

Découverte de Scikit-learn
Parfois, le moyen le plus efficace pour découvrir un nouvel outil consiste à jouer avec, d’autant
plus si l’outil est complexe. Il n’est donc pas inutile de commencer par essayer le paquetage
Scikit-learn et les opérations mathématiques complexes qu’il permet. C’est ce que nous vous
proposons de faire dans les sections qui suivent, tout en découvrant au passage les concepts
fondamentaux de l’outil, et son grand intérêt dans le domaine de la datalogie.

Les classes de Scikit-learn


Il est indispensable d’avoir compris comment fonctionnent les classes de ce paquetage pour
bien pouvoir l’utiliser. La plupart des datalogues ont jeté leur dévolu sur ce paquetage pour
leurs travaux d’apprentissage machine et de datalogie. Il réunit une vaste palette
d’algorithmes d’apprentissage, de fonctions d’erreur et de procédures de test.
Au départ, Scikit-learn définit un petit nombre de classes de base sur lesquelles s’appuient
tous les algorithmes. La classe ancestrale BaseEstimator est la mère de toutes les autres.
Quatre classes couvrent toutes les possibilités d’apprentissage machine de base :
» Classification
» Régression
» Regroupement en groupes (clusters)
» Transformations

Chaque classe de base définit ses propres méthodes et attributs. Les possibilités
fondamentales de traitement des données d’apprentissage sont en revanche garanties par
quelques séries de méthodes et attributs correspondant aux interfaces. Ces interfaces
constituent un guichet d’accès par programmation, c’est-à-dire une API (Application
Programming Interface). Ce sont ces interfaces qui garantissent l’homogénéité des méthodes
et attributs entre les différents algorithmes du paquetage. Ces interfaces orientées objet de
Scikit-learn sont au nombre de quatre :
» estimator : pour ajuster les paramètres en les apprenant à partir des données grâces à
l’algorithme ;
» predictor : pour générer des prédictions à partir des paramètres ajustés ;
» transformator : pour transformer les données en utilisant les paramètres ajustés ;
» model : pour rendre compte de la qualité d’ajustement ou d’autres points de mesure.

Dans le paquetage, les algorithmes sont groupés selon la classe de base avec plusieurs objets
d’interface dans des modules. Chaque module constitue une spécialisation pour la résolution
d’un certain genre d’apprentissage machine. C’est ainsi que le module linear_model sert à la
modélisation linéaire alors que le module metrics sert à la métrologie et au suivi des pertes.
Lorsque vous cherchez un algorithme dans Scikit-learn, il faut d’abord trouver le module
contenant le genre d’algorithmes dont vous avez besoin puis sélectionner celui-ci dans la liste
du contenu du module. En général, l’algorithme correspond à une classe dont les méthodes et
attributs sont déjà déclarés parce que ce sont les mêmes que les autres algorithmes de Scikit-
learn.
Prévoyez un peu de temps pour prendre en main les classes de Scikit-learn tout en sachant que
l’interface API est la même pour tous les outils du paquetage. Il suffit d’apprendre une classe
pour savoir utiliser les autres. La meilleure approche consiste donc à apprendre entièrement
une classe pour réutiliser vos connaissances avec les autres.

Sélection d’applications pour la datalogie


Pour obtenir de bons résultats, il est capital de définir la façon dont vous comptez utiliser la
datalogie. Vous pouvez par exemple adopter l’interface estimator pour régler les problèmes
suivants :
» problèmes de classification : pour estimer qu’une nouvelle observation appartient ou
non à un certain groupe ;
» problèmes de régression : pour estimer la valeur d’une nouvelle observation.

Dans ce cas précis, il s’agit d’utiliser la méthode fit(X, y), X correspondant au tableau
bidimensionnel de prédicteurs (donc le jeu d’observation à apprendre) alors que y est le
résultat, soit un tableau à une dimension.
En appliquant la méthode fit(), vous mettez en relation l’information dans X avec y. Une
nouvelle donnée ayant les mêmes caractéristiques que X permet de déduire y correctement.
Certains des paramètres sont estimés par la méthode fit() en interne. Vous pouvez ainsi
distinguer les paramètres qui sont appris des hyperparamètres qui sont définis par vous au
moment de créer l’instance de l’apprenant.
Cette instanciation consiste à associer une classe Scikit-learn à une variable Python. En plus
des hyperparamètres, vous pouvez stipuler des paramètres de travail, par exemple, la
normalisation demandée ou la semence de valeurs aléatoires afin d’obtenir les mêmes résultats
dans tous les appels travaillant sur les mêmes données d’entrée.
Voyons un exemple de régression linéaire, une opération d’apprentissage machine élémentaire
très fréquente. Nous allons utiliser des données d’entrée fournies avec le paquetage Scikit-
learn. Le jeu nommé Boston, déjà rencontré, contient des variables prédicteurs que nous
pouvons confronter au prix des maisons, afin de générer un prédicteur qui va pouvoir estimer
une nouvelle maison à partir de ses caractéristiques.

from sklearn.datasets import load_boston


boston = load_boston()
X, y = boston.data,boston.target
print(“X:%s y:%s” % (X.shape, y.shape))

Les dimensions renvoyées pour les deux variables X et y sont les suivantes :

X:(506, 13) y:(506,)

L’affichage nous apprend que les deux tableaux ont le même nombre de lignes et que X
comporte 13 caractéristiques. La méthode shape() analyse un tableau et renvoie sa
dimension.
Le nombre de lignes doit être le même pour X et pour y. Vous devez également vous assurer
que X et y correspondent parce que l’algorithme apprend en confrontant les lignes de X avec
l’élément correspondant de y. Si vous naviguez au hasard dans les tableaux, rien ne peut être
appris.
Les caractéristiques de X, c’est-à-dire les colonnes, s’appellent également des variables dans le
monde des statistiques, ou des caractéristiques dans le monde du mécapprentissage ou
apprentissage machine.

Une fois que vous avez importé la classe LinearRegression, vous pouvez instancier une
variable nommée hypothesis en choisissant le mode de normalisation. Ici, nous paramétrons
le zéro de moyenne et une déviation standard unitaire pour toutes les variables, opération
statistique permettant que toutes les variables soient au même niveau. Nous pouvons ensuite
estimer les paramètres à apprendre.

from sklearn.linear_model import LinearRegression


hypothesis = LinearRegression(normalize=True)
hypothesis.fit(X, y)
print(hypothesis.coef_)

Le résultat est l’affichage des coefficients de notre hypothèse de régression linéaire :

[-1.07170557e-01 4.63952195e-02 2.08602395e-02 2.68856140e+00


-1.77957587e+01 3.80475246e+00 7.51061703e-04 -1.47575880e+00
3.05655038e-01 -1.23293463e-02 -9.53463555e-01 9.39251272e-03
-5.25466633e-01]

Après ajustement (fitting), la variable hypothesis contient les paramètres appris, et vous
pouvez les visualiser au moyen des méthodes coef_, comme c’est habituel dans tous les
modèles linéaires dans lesquels la sortie est une sommation des variables pondérées par
coefficients. Cette activité d’ajustement peut également être considérée comme
l’apprentissage d’ajustement, c’est-à-dire l’apprentissage d’un algorithme machine.
Une hypothèse est une façon de décrire un algorithme qui a été entraîné avec des données.
L’hypothèse définit une représentation possible de y en fonction de X que vous testez au
niveau de la validité. C’est donc une hypothèse tant en termes scientifiques, qu’en termes
d’apprentissage machine.
Deux autres classes importantes de l’objet en dehors de la classe de l’estimateur sont celles du
prédicteur et du modèle. La classe predictor sert à prédire la probabilité d’un certain
résultat, en obtenant ce résultat pour les nouvelles observations avec ses méthodes predict()
et predict_proba(), comme dans cet extrait :

import numpy as np
new_observation = np.array([1, 0, 1, 0, 0.5, 7, 59,
6, 3, 200, 20, 350, 4],
dtype=float).reshape(1, -1)
print(hypothesis.predict(new_observation))

L’observation unique est ainsi convertie en une prédiction :

[25.8972784]

Pour que la prédiction soit valide, il est nécessaire que les nouvelles observations aient le
même nombre de caractéristiques que l’élément X en entrée, et dans le même ordre.
Le modèle de classe vous informe au sujet de la qualité de l’ajustement grâce à sa méthode
score() :

hypothesis.score(X, y)

La qualité est exprimée en tant que valeur à virgule flottante :

0.7406077428649428

Dans l’exemple, score() renvoie le coefficient de détermination R au carré pour la prédiction.


Il s’agit d’une mesure entre zéro et un qui compare le prédicteur à une moyenne simple. Les
valeurs hautes indiquent que le prédicteur fonctionne correctement. Les algorithmes
d’apprentissage différents n’utilisent pas les mêmes fonctions de mesure. Vous pouvez
consulter la documentation de chaque algorithme ou demander l’aide dans la console Python :

help(LinearRegression)

Enfin, la classe de transformation applique des transformations à d’autres tableaux de données


en s’appuyant sur la phase d’ajustement. Il n’y a pas de méthode transform() pour la
régression linéaire, mais la plupart des autres algorithmes de prétraitement en disposent.
C’est ainsi que MinMaxScaler() (se trouve dans le module préprocesseur de Scikit-learn) est
capable de transformer des valeurs dans une plage spécifiée par une valeur minimale et une
maximale, en apprenant la formule de transformation à partir d’un tableau d’exemple :

from sklearn.preprocessing import MinMaxScaler


scaler = MinMaxScaler(feature_range=(0, 1))
scaler.fit(X)
print(scaler.transform(new_observation))

L’exécution de ce code présente les valeurs transformées pour les observations :

[[0.01116872 0. 0.01979472 0. 0.23662551 0.65893849


0.57775489 0.44288845 0.08695652 0.02480916 0.78723404 0.88173887
0.06263797]]

Ici, nous appliquons les valeurs min et max qui ont été apprises auprès de X à la nouvelle
variable new_observation, puis renvoyons la transformation.

La technique de hachage
Scikit-learn réunit la plupart des structures de données et fonctions dont vous aurez besoin
pour réaliser vos projets de datalogie. Vous y trouverez même des classes pour les problèmes
les plus épineux.
Si vous devez par exemple traiter des objets texte, une des solutions les plus pratiques offertes
par Scikit-learn se fonde sur la technique du hachage (hashing). Nous avons vu comment
préparer du texte avec la technique du sac de mots dans le Chapitre 8 et pondérer des mots
avec la transformation TF-IDF. Ces transformations sont puissantes, mais elles ne sont
applicables que si la totalité du texte est connue et accessible dans la mémoire de la machine.
Il est beaucoup plus ardu de traiter des flux de texte générés en ligne, par exemple ceux
obtenus dans les réseaux sociaux ou puisés dans d’énormes référentiels en ligne. Il devient
vraiment difficile de convertir ce genre de volume de texte dans une matrice de données pour
les analyser. Face à un tel problème, vous tirerez grand bénéfice de la technique du hachage,
qui procure quelques avantages :
» le hachage aide à gérer d’énormes matrices de données alimentées par du texte à la
volée ;
» vous corrigez plus facilement des valeurs ou des variables inattendues dans les données
texte ;
» vous pouvez construire des algorithmes adaptatifs pour de vastes collections de
documents.

Domaines d’emploi de la technique de hachage


Les fonctions de hachage servent à transformer n’importe quelles données d’entrée en
données de sortie ayant des caractéristiques prévisibles. En général, la valeur renvoyée est liée
à un intervalle particulier, dont les bornes vont par exemple d’une valeur négative à une valeur
positive, ou seulement d’une valeur positive à une autre. Cela revient un peu à appliquer un
standard à vos données : quelles que soient les valeurs fournies, le produit sera toujours le
même.
Le caractère le plus utile des fonctions de hachage est de toujours fournir la même valeur
numérique en partant d’une certaine valeur d’entrée. Ce sont donc des fonctions
déterministes. Si vous fournissez par exemple en entrée un mot tel que chien, la fonction de
hachage renverra toujours la même valeur numérique.
La fonction de hachage représente donc une sorte de générateur de code secret, et transforme
tout ce qu’elle touche en nombre. En revanche, elle se distingue d’un code secret par le fait
que vous ne pouvez pas revenir à la valeur de départ à partir de celle d’arrivée. Dans de rares
cas, il arrive que deux mots différents produisent la même valeur, ce qui correspond à une
collision de hachage.

Démonstration de la technique de hachage


Il existe de nombreuses fonctions de hachage, les deux plus utilisées étant MD5 (qui sert
souvent de somme de contrôle pour les fichiers à télécharger) et SHA (qui sert en
cryptographie). Python est doté d’une fonction de hachage interne portant le nom hash(). Vous
pouvez l’utiliser pour comparer des objets de données avant de les stocker dans des
dictionnaires. Voyons par exemple ce que donne le hachage du mot Python :

print(hash(‘Python’))

Le résultat est un grand nombre :

745803682812927152

Sur votre machine, la valeur produite sera sans doute différente. Ne vous en inquiétez pas. Les
fonctions de hachage peuvent donner des résultats différents d’un ordinateur à l’autre. Si vous
avez vraiment besoin de résultats constants, utilisez seulement les fonctions de hachage Scikit-
learn ; vous serez assuré que les valeurs produites seront les mêmes sur toutes les machines.
Une fonction de hachage de Scikit-learn peut également renvoyer un index dans les limites
d’une plage positive spécifiée. Vous pouvez obtenir le même style de résultat manuellement
dans Python en utilisant l’opérateur modulo :

print(abs(hash(‘Python’)) % 1000)

Le résultat est alors une valeur de hachage entière beaucoup moins longue :

152

Lorsque vous demandez d’obtenir le reste de la valeur absolue du résultat de la fonction de


hachage, vous obtenez une valeur qui ne sera jamais supérieure à celle utilisée pour la
division. Pour illustrer cela, supposons que nous ayons besoin de transformer une chaîne de
texte trouvée sur Internet en un vecteur numérique (un vecteur de caractéristiques), ce qui
permet ensuite de vous en servir pour débuter un nouveau projet de mécapprentissage. Une
stratégie efficace dans ce cas consiste à adopter le codage un sur N, one-hot, abrégé en oh.
Son produit est un sac de mots. Voici les étapes à prévoir pour cette opération à partir de la
chaîne Python for data science et obtenir un vecteur :
1. Affectation d’un nombre arbitraire à chaque mot, par exemple Python=0, for=1,
data=2 et science=3.
2. Initialisation du vecteur en comptant le nombre de mots uniques qui ont été
associés à un code dans la première étape.
3. Utilisation des codes de l’étape 1 comme index pour insérer les valeurs dans le
vecteur, en assignant la valeur 1 dès qu’il y a coïncidence avec un mot de la
phrase.
Le vecteur de caractéristiques qui en résulte correspond à la séquence [1,1,1,1] et comporte
donc exactement quatre éléments. Vous avez ainsi démarré votre processus d’apprentissage,
en informant le programme qu’il doit attendre des séquences de quatre caractéristiques texte.
Mais voici que survient une autre phrase ; vous devez convertir la phrase « Python for
machine learning » en un autre vecteur numérique. De ce fait, les deux nouveaux mots
« machine » et « learning » sont à traiter. Voici comment créer les nouveaux vecteurs :
1. Affectation des nouveaux codes : machine=4 et learning=5, ce qui correspond à
l’opération de codage.
2. Agrandissement du vecteur précédent pour accueillir les nouveaux mots :
[1,1,1,1,0,0].
3. Calcul du vecteur pour la nouvelle chaîne : [1,1,0,0,1,1].
Ce codage un sur N ou one-hot est assez efficace parce qu’il produit des vecteurs de
caractéristiques bien ordonnés.

from sklearn.feature_extraction.text import *


oh_enconder = CountVectorizer()
oh_enconded = oh_enconder.fit_transform([
‘Python for data science’,’Python for machine learning’])

print(oh_enconder.vocabulary_)

L’exécution montre que le programme produit un dictionnaire qui contient des paires
constituées des mots et de leur codage :

{‘python’: 4, ‘for’: 1, ‘data’: 0, ‘science’: 5,


‘machine’: 3, ‘learning’: 2}

Cette stratégie n’est plus applicable et échoue lorsque le projet reçoit en entrée des éléments
variant beaucoup. Cela est souvent le cas en datalogie lorsqu’il s’agit de travailler du texte ou
des caractéristiques symboliques. Le flux Internet ou un autre environnement en ligne peut de
façon soudaine produire de nouvelles données. Dans ce cas, les fonctions de hachage sont
mieux adaptées pour prendre en charge des données d’entrée imprévisibles :
1. Définition d’une plage de sortie de la fonction de hachage. Tous les vecteurs de
caractéristiques vont l’utiliser. Dans l’exemple, la plage de valeurs va de 0 à 24.
2. Calcul d’index pour chaque mot de la chaîne au moyen de la fonction de
hachage.
3. Affectation d’une valeur unitaire aux positions des vecteurs en fonction des
index des mots.
Vous pouvez définir un mécanisme de hachage en Python très simplement : il suffit de créer
une fonction puis de vérifier les résultats à partir des deux chaînes servant de test :

string_1 = ‘Python for data science’


string_2 = ‘Python for machine learning’

def hashing_trick(input_string, vector_size=20):


feature_vector = [0] * vector_size
for word in input_string.split(‘ ‘):
index = abs(hash(word)) % vector_size
feature_vector[index] = 1
return feature_vector

Voici comment tester les deux chaînes :

print(hashing_trick(
input_string=’Python for data science’,
vector_size=20))

Voici la première chaîne une fois codée comme vecteur :

[0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1]

Si vous faites des essais, les résultats ne seront peut-être pas les mêmes que ceux imprimés
dans ce livre, en raison des différences de fonctionnement du hachage. Vous pouvez ensuite
afficher la seconde chaîne une fois codée :
print(hashing_trick(
input_string=’Python for machine learning’,
vector_size=20))

Voici les résultats pour la seconde chaîne :

[0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0]

La lecture des vecteurs de caractéristiques amène deux remarques :


» Vous ne savez pas où se situe chaque mot. Si vous devez pouvoir revenir en arrière pour
retrouver les mots, il faut stocker les relations entre mots et valeurs de hachage de façon
distincte. Vous pouvez par exemple utiliser un dictionnaire où les clés sont les valeurs de
hachage et les valeurs sont les mots.
» Lorsque le paramètre vector_size reçoit une petite valeur, par exemple 10, de
nombreux mots vont se chevaucher dans la même position dans la liste qui incarne le
vecteur de caractéristiques. Pour limiter ce chevauchement, vous devez créer des limites
de fonctions de hachage supérieures au nombre d’éléments que vous allez ensuite indexer.

Dans l’exemple, la plupart des vecteurs correspondent à des entrées valant zéro, ce qui est un
gaspillage d’espace mémoire, en comparaison de la technique un sur N. Une façon de résoudre
ce problème consiste à adopter les matrices creuses ; c’est le sujet de la prochaine section.

Matrices creuses et sélection déterministe


Lorsque les données offrent un faible nombre de valeurs, c’est-à-dire quand la plupart des
valeurs de la matrice valent zéro, il est intéressant d’utiliser une matrice creuse. Une matrice
creuse (sparse matrix) stocke pour toutes les cellules de la matrice les coordonnées des
cellules et de leurs valeurs et non toute l’information. Lorsque l’application demande des
données pour une cellule vide, la matrice renvoie la valeur zéro, après avoir cherché les
coordonnées et n’avoir rien trouvé. Partons d’un vecteur d’exemple :

[1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0]

Le code suivant le convertit en une matrice creuse :

from scipy.sparse import csc_matrix


print(csc_matrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1,
0]))

Voici la représentation fournie par csc_matrix() :

(0, 0) 1
(0, 5) 1
(0, 16) 1
(0, 18) 1

La première colonne montre qu’il s’agit de coordonnées (des tuples d’indices ligne, colonne) et
l’autre colonne est la valeur de la cellule.
Plusieurs matrices et plusieurs structures de type matrice creuse sont disponibles dans le
paquetage SciPy. Chacune propose de stocker les données d’une manière différente et
fonctionne de façon spécifique. Certaines sont très efficaces pour les opérations de tranchage
alors que d’autres sont meilleures pour les calculs. Un bon choix est souvent csc_matrix()
qui est une matrice compressée se fondant sur les lignes. La plupart des algorithmes de Scikit-
learn l’acceptent en entrée, et elle est optimale pour les opérations sur les matrices.
En tant que datalogue, vous n’aurez pas à programmer votre propre version de la technique de
hachage, sauf si vous avez une idée originale à ce niveau. Scikit-learn propose la classe
HashingVectorizer() qui permet rapidement de transformer une collection de textes en une
matrice creuse en utilisant la technique de hachage. Voici un script qui réplique l’exemple
précédent :

import sklearn.feature_extraction.text as txt


htrick = txt.HashingVectorizer(n_features=20,
binary=True, norm=None)
hashed_text = htrick.transform([‘Python for data science’,
‘Python for machine learning’])
hashed_text

Python indique la taille de la matrice créée et le nombre d’éléments contenus :

<2x20 sparse matrix of type ‘<class ‘numpy.float64’>’


with 8 stored elements in Compressed Sparse Row format>

Dès qu’un nouveau texte arrive, CountVectorizer() (dans oh_enconder) peut le transformer
en se basant sur le schéma de codage précédent, dans lequel les nouveaux mots n’existaient
pas ; le résultat est de ce fait un vecteur de zéro vide. Pour le vérifier, il suffit de transformer la
matrice creuse en une matrice pleine avec todense() :

oh_enconder.transform([‘Nouveau texte arrivé’]).todense()

Nous confirmons que la matrice est dorénavant vide :

matrix([[0, 0, 0, 0, 0, 0]], dtype=int64)

Il est intéressant de comparer le résultat de CountVectorizer() avec celui de


HashingVectorizer(), ce dernier gardant toujours de la place pour les nouveaux mots dans la
matrice :

htrick.transform([‘Nouveau texte arrivé’]).todense()

La matrice peuplée par HashingVectorizer() représente les nouveaux mots :

matrix([[1., 0., 0., 1., 0., 0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0.,
0., 0., 0., 1.]])

Dans le pire des cas, un nouveau mot va se placer à une position déjà occupée. Deux mots
différents seront traités de la même façon par l’algorithme, mais cela ne va pas sensiblement
modifier ses performances.
La fonction HashingVectorizer() est idéale dès que les données ne peuvent pas toutes être
stockées en mémoire et que les caractéristiques ne sont pas fixées. Dans tous les autres cas,
vous utilisez la fonction CountVectorizer() qui est plus intuitive.

Considérations de temps et de performances


Puisque le livre présente des sujets de plus en plus complexes, comme les classes
d’apprentissage de Scikit-learn et les matrices creuses, vous pourriez commencer à vous
demander en quoi tous ces traitements peuvent impacter les performances de l’application.
Ces contraintes ont un effet tant sur la durée d’exécution que sur la quantité de mémoire
nécessaire.
Exploiter au mieux les ressources de traitement d’une machine est un art, l’art de
l’optimisation, et sa maîtrise demande du temps. Vous pouvez néanmoins commencer à le
pratiquer en effectuant quelques mesures de performances pour localiser les causes des
ralentissements. Chronométrer les opérations, mesurer l’évolution de l’empreinte mémoire en
fonction de l’ajout de données, et lancer une transformation des données peuvent aider à
repérer les goulets d’étranglement du code et partant à envisager des alternatives.
Comme décrit dans le Chapitre 5, Jupyter est l’environnement parfait pour expérimenter,
bidouiller et améliorer votre code. En travaillant sur des blocs de code bien délimités, en
enregistrant les résultats et en ajoutant des commentaires et notes de production, vous allez
faire émerger vos solutions de datalogie de façon maîtrisée et reproductible.

Chronométrage avec timeit


L’étude de l’exemple précédent dans la section décrivant la technique de hachage de ce
chapitre nous a permis de comparer deux approches d’encodage d’informations textuelles dans
une matrice, pour répondre à deux styles de besoins :
» CountVectorizer : encode le texte dans une matrice mais ne peut gérer l’irruption de
nouvelles données dans le texte.
» HashingVectorizer : offre la souplesse requise en cas d’irruption de nouvelles données,
mais moins optimale que les techniques fondées sur le hachage.

Les avantages de chaque approche en termes de gestion des données étant clairs, vous vous
demanderez sans doute ensuite quel est l’impact de chacune sur la vitesse de traitement et
l’empreinte mémoire.
Au niveau de la vitesse, Jupyter vous propose une solution prête à l’emploi consistant à
combiner deux commandes magiques, celle en ligne %timeit et celle de cellule %%timeit :
» %timeit : calcule le meilleur temps d’exécution d’une instruction ;
» %%timeit : calcule le meilleur temps d’exécution de la séquence d’instructions d’une
cellule de calepin, compte non tenu de celle de la ligne dans laquelle se trouve la
commande magique (ce qui permet de s’en servir pour initialiser l’opération).

Les deux commandes procèdent à un nombre d’essais exprimé par r et répètent ce train
d’essais n fois. Si vous spécifiez les deux paramètres –r et –n, l’application cherche à favoriser
la réponse la plus rapide.
Voici par exemple comment chronométrer l’opération d’affectation d’une liste de 10**6 valeurs
ordinales par compréhension de liste :

%timeit l = [k for k in range(10**6)]

Voici les résultats :

111 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Voici le même test en choisissant les paramètres r et n pour contrôler le

nombre de répétitions et d’essais :


%timeit –n 20 –r 5 l = [k for k in range(10**6)]

Après avoir patienté un peu, nous obtenons le résultat :

111 ms ± 1.45 ms per loop (mean ± std. dev. of 5 runs, 20 loops each)

En guise de comparaison, chronométrons l’affectation des valeurs dans une boucle de


répétition for. Puisque ce genre de boucle a besoin d’une cellule complète, nous utilisons la
commande magique de cellule, %%timeit. Précisons que la ligne qui affecte la valeur
de 10**6 à une variable n’est pas prise en compte dans le calcul.

%%timeit
l = list()
for k in range(10**6):
l.append(k)

Voici le résultat du chronométrage :

164 ms ± 2.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Nous en déduisons que la compréhension de liste est 50 % plus rapide que la boucle for.
Refaisons maintenant le même test en comparant deux stratégies de codage du texte.
Commençons par préparer les décors :

import sklearn.feature_extraction.text as txt


htrick = txt.HashingVectorizer(n_features=20,
binary=True,
norm=None)
oh_enconder = txt.CountVectorizer()
texts = [‘Python for data science’,
‘Python for machine learning’]

N. d. T. : Dans la version française, nous conservons les phrases de test en anglais qui
conviennent tout autant à la démonstration.
Nous avons ainsi chargé les classes et nous les avons instanciées. Testons la première des deux
approches :

%timeit oh_enconded = oh_enconder.fit_transform(texts)

Voici le temps consommé pour l’encodeur de mots basé sur CountVectorizer :

719 µs ± 14.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Procédons de même avec HashingVectorizer :

%timeit hashing = htrick.transform(texts)

Les performances sont environ six fois meilleures (1 000 µs valent une milliseconde) :

113 µs ± 644 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Conclusion : le hachage est plus rapide que le codage un sur N (one hot). Il faut dire que le
hachage utilise un algorithme optimisé qui garde trace de la façon dont les mots ont été codés,
ce que ne fait pas l’autre algorithme.
L’outil Jupyter est un excellent environnement pour mesurer les performances d’une solution
de datalogie. Vous pouvez faire vos mesures sur ligne de commande ou bien dans un script
lancé depuis l’environnement d’édition en important la classe timeit puis en vous servant de
la fonction timeit(), en fournissant les paramètres d’entrée sous forme d’une chaîne.
Si votre commande à chronométrer a besoin de variables, de classes ou de fonctions qui ne
sont pas disponibles dans la livraison Python standard, par exemple les classes de Scikit-learn,
vous pouvez les indiquer en tant que second paramètre d’entrée. Il suffit de construire une
chaîne décrivant à Python comment importer tous les objets requis depuis l’environnement
principal, comme le montre cet exemple :

import timeit
cumulative_time = timeit.timeit(
“hashing = htrick.transform(texts)”,
“from __main__ import htrick, texts”,
number=10000)
print(cumulative_time / 10000.0)

Cet extrait produit le résultat numérique suivant :

0.0001185207694947394
INSTALLATION AVEC PIP OU AVEC CONDA

Un grand nombre de paquetages peuvent être ajoutés à Python. La plupart se présentent sous la
forme de modules à télécharger à part. Certains proposent un installateur pour Windows, ce qui peut
apparemment simplifier l’installation, mais la plupart des paquetages comptent sur l’outil PIP
(Preferred Installer Program), accessible directement depuis la ligne de commande Python.
Pour vous en servir, ouvrez une fenêtre Anaconda Prompt. Si vous avez par exemple besoin d’installer
NumPy, saisissez directement la commande pip install numpy. Le logiciel sera téléchargé avec tous
les paquetages dont il dépend puis installé. Vous pouvez même choisir la version à installer en
écrivant par exemple pip install -U numpy==1.14.5. ou plus couramment demander la mise en
place de la plus récente version d’un paquetage déjà présent en écrivant ceci :

pip install –U numpy

Puisque vous avez installé Anaconda, vous pouvez remplacer l’outil PIP par conda qui est encore plus
efficace parce qu’il réaligne les numéros de versions de tous les autres paquetages. Il est donc
capable de mettre à jour et même de rétrograder un paquetage existant pour le rendre compatible.
Pour utiliser conda, vous travaillez depuis la même fenêtre Anaconda Prompt. Saisissez par exemple
conda install numpy. L’outil va analyser le système, indiquer les changements puis demander s’il
doit s’exécuter. Vous frappez la touche Y si vous êtes d’accord. L’outil permet bien sûr de mettre à
jour les paquetages existants avec conda update numpy ou tout votre système avec conda update -
all.
Procéder à une installation ou une mise à jour tout en utilisant Jupyter est un peu plus complexe. Jake
Vanderplas de l’université de Washington a publié un article intéressant à ce sujet (https://jake-
vdp.github.io/blog/2017/12/05/installing-python-packages-from-jupyter/). Il propose
plusieurs astuces pour installer et mettre à jour des paquetages depuis l’interface Jupyter. Mais tant
que vous n’avez pas l’outil bien en main, mieux vaut procéder aux installations et aux mises à jour
avant de démarrer Jupyter.

Utilisation du profileur mémoire


Les tests de performances applicatives que nous venons de réaliser permettent également de
se renseigner sur l’empreinte (encombrement) mémoire du programme. En analysant votre
consommation de l’espace mémoire, vous pouvez trouver la cause de certains problèmes au
niveau du traitement des données ou de leur transmission aux algorithmes apprenants. Il suffit
d’utiliser le paquetage nommé memory_profiler, qui n’est pas installé d’office avec Python.
Vous pouvez en demander l’installation directement de l’intérieur d’une cellule de Jupyter
Notebook. Voyez aussi à ce sujet la page de Jake Vanderplas citée dans l’encadré sur
l’installation avec PIP et conda. Voici les deux commandes pour installer le profileur :

import sys
!{sys.executable} -m pip install memory_profiler

Au début de chaque démarrage d’une session Jupyter Notebook que vous désirez chronométrer
et surveiller, vous exécutez la commande suivante :

%load_ext memory_profiler

Une fois ces préparations réalisées, vous pouvez facilement inspecter l’empreinte mémoire au
moyen de la commande magique %memit :

hashing = htrick.transform(texts)
%memit dense_hashing = hashing.toarray()

L’affichage vous informe sur le volume de mémoire occupé et sa variation par rapport à
l’instruction précédente ( « increment » ) :

peak memory: 76.52 MiB, increment: 0.09 MiB

Pour une étude plus détaillée de l’utilisation mémoire, il faut stocker le code source d’une
cellule sur disque, puis lancer le profilage au moyen de la commande magique locale %mprun
en l’appliquant à une fonction définie dans ce fichier externe. En effet, cette commande
magique ne peut traiter qu’un script Python externe. Vous obtenez ainsi un rapport détaillé,
ligne par ligne, comme le montre cet exemple :

%%writefile example_code.py
def comparison_test(text):
import sklearn.feature_extraction.text as txt
htrick = txt.HashingVectorizer(n_features=20,
binary=True,
norm=None)
oh_enconder = txt.CountVectorizer()
oh_enconded = oh_enconder.fit_transform(text)
hashing = htrick.transform(text)
return oh_enconded, hashing

MOINS DE MÉMOIRE ET PLUS DE PERFORMANCES

Vos structures de données sont surtout des tableaux (array) de NumPy ou des DataFrame de pandas.
Ce sont des structures différentes, l’une se contentant de stocker les données dans des matrices alors
que l’autre propose des structures de stockage complexes sous différents formats. Cependant, le type
DataFrame se fonde sur le type array de NumPy. De ce fait, vous pouvez réduire l’empreinte
mémoire et augmenter les performances en étudiant le fonctionnement des tableaux NumPy et leur
utilisation par pandas.
Les tableaux de NumPy stockent les valeurs dans des blocs mémoire voisins. Ce voisinage permet à
Python d’accéder plus vite aux données, par exemple pour effectuer un tranchage. Le souci est le
même qu’avec le phénomène de fragmentation disque : si les données sont disséminées en différents
endroits du disque, elles occupent plus d’espace et sont moins rapidement accessibles.
En fonction de vos besoins, vous pouvez demander de faire stocker les données des tableaux par
lignes (c’est le choix standard aussi bien dans NumPy que dans les langages C et C++), ou par
colonnes. Les contenus des cellules sont stockés en mémoire les uns après les autres de façon
contiguë. Vous pouvez donc enregistrer votre tableau soit ligne par ligne, pour traiter plus rapidement
dans ce sens, soit colonne par colonne. Ces détails de fonctionnement sont masqués, mais ils sont
essentiels car d’eux dépend l’efficacité des tableaux NumPy en datalogie, domaine dans lequel on
utilise beaucoup les matrices numériques pour traiter l’information. C’est pour cette raison que tous
les algorithmes de Scikit-learn attendent en entrée des structures du type tableau NumPy, et ce style
de tableau est à type fixe ; un tel tableau ne peut être que du même type que la séquence de
données et ne peut pas en varier).
La structure DataFrame de pandas revient à une collection bien organisée de tableaux NumPY. Les
variables définies dans votre cadre DataFrame sont regroupées et compactées dans des tableaux
pour les différents types. Toutes les variables de type entier sont regroupées dans un bloc IntBlock,
toutes les variables flottantes dans un tableau FloatBlock et toutes les autres dans ObjectBlock. De ce
fait, lorsque vous voulez traiter une seule variable, vous accédez à tout son groupe. Première
conséquence : il est préférable d’appliquer vos opérations à toutes les variables du même type en
même temps. Une autre conséquence est que les variables de type chaîne sont beaucoup plus
pénalisantes en empreinte mémoire et en temps de calcul. Même une série très légère telle qu’une
liste de noms de couleurs déclenche le stockage dans une chaîne complète d’au moins 50 octets, dont
la manipulation est fastidieuse avec le moteur de NumPy. Comme nous l’avons indiqué dans le
Chapitre 7, il est utile de tenter de transformer les données de type chaîne en variables catégorielles ;
vous chaînes deviennent ainsi des valeurs numériques, ce qui réduit énormément l’empreinte
mémoire et améliore les performances.

Le compte rendu détaillé apparaît dans un panneau dans le bas de la fenêtre de Notebook par
défaut :

Filename: C:\Users\Sergent\PYDASC\example_code.py

Line # Mem usage Increment Line Contents


================================================
1 72.2 MiB 72.2 MiB def comparison_test(text):
2 72.2 MiB 0.0 MiB import sklearn.feature_extraction.text as
txt
3 72.2 MiB 0.0 MiB htrick =
txt.HashingVectorizer(n_features=20,
4 72.2 MiB 0.0 MiB
binary=True,
5 72.2 MiB 0.0 MiB norm=None)
6 72.2 MiB 0.0 MiB oh_enconder = txt.CountVectorizer()
7 72.2 MiB 0.0 MiB oh_enconded =
oh_enconder.fit_transform(text)
8 72.2 MiB 0.0 MiB hashing = htrick.transform(text)
9 72.2 MiB 0.0 MiB return oh_enconded, hashing

L’usage mémoire est celui constaté après exécution de la ligne mentionnée ; la colonne
increment indique la variation par rapport à la ligne précédente.

Exécution parallèle et multicœur


De nos jours, quasiment tous les ordinateurs sont multicœurs (au moins deux processeurs dans
le même circuit intégré), et certaines machines comportent même plusieurs circuits
processeurs. Le langage Python a été inventé à une époque où les machines n’avaient qu’un
seul processeur. Là se situe le problème.
Les projets de datalogie sont gourmands en puissance de traitement. C’est notamment le cas
des tests répétitifs sur différentes matrices de données. Rappelons que le traitement d’un
grand volume de données suppose de répéter observation après observation les mêmes
transformations intensives, par exemple pour vérifier les identités et différencier les
différentes parties d’une matrice.
En mettant à profit plusieurs cœurs de traitement, vous accélérez les calculs d’un facteur
presque égal au nombre de cœurs. Avec quatre cœurs, vous travaillez presque quatre fois plus
vite, si l’on écarte la surcharge imposée par le démarrage d’un nouveau processus. En effet,
chaque nouvelle instance de Python doit être configurée avec les informations en mémoire puis
démarrée, ce qui explique que l’amélioration est inférieure à celle théorique. Il est donc
indispensable d’apprendre à bien exploiter plusieurs processeurs, car cela permet d’augmenter
le nombre d’analyses dans le même laps de temps, et donc d’accélérer vos activités.
Le multitraitement consiste à dupliquer le même code et le même contenu mémoire dans de
nouvelles instances de l’interpréteur Python, instances nommées travailleurs (workers).
Chaque instance produit ses résultats, qui sont centralisés vers la console à l’origine de
l’exécution. Si votre première instance occupe déjà quasiment tout l’espace mémoire
disponible, vous ne pouvez pas créer de nouvelle instance et vous provoquerez des
débordements mémoire.

Parallélisme multicœur
Pour profiter du parallélisme multicœur dans Python, vous intégrez le paquetage Scikit-learn
avec le paquetage nommé joblib qui se charge des opérations gourmandes en temps ; c’est
par exemple le cas de la réplication de modèles pour valider des résultats ou de la recherche
des meilleurs hyperparamètres. Scikit-learn permet en particulier de réaliser du
multitraitement pour les opérations suivantes :
» Cross-validation : test de résultat d’une hypothèse d’apprentissage au moyen de
données d’apprentissage et de test différentes.
» Recherche grille : modification systématique des hyperparamètres d’une hypothèse
d’apprentissage et test des résultats.
» Prédiction multilabel : exécution répétée d’un algorithme avec plusieurs cibles, lorsque
de nombreux résultats différents doivent être prévus en même temps.
» Méthodes d’apprentissage machine d’ensemble : modélisation d’une grande palette
de classificateurs, tous indépendants, comme dans la modélisation basée sur
RandomForest.

Pour profiter de ce multitraitement, il suffit d’activer l’option de parallélisme en donnant au


paramètre nommé n_jobs une valeur supérieure à 1 (en fournissant la valeur -1, vous faites
utiliser tous les cœurs disponibles).
Si vous ne lancez pas l’exécution du code depuis la console ou depuis l’outil Jupyter Notebook,
vous devez absolument séparer le code de toutes les directives d’import de paquetage et
affectations de variables globales dans le script. Pour ce faire, vous ajoutez la commande
suivante au début du code à paralléliser :

if __name__==’__main__’:
Cette instruction teste si le programme a été lancé directement, ou s’il a été démarré par une
console Python active. Vous évitez ainsi toute confusion ou erreur du processus multiparallèle,
par exemple un appel répété en boucle du parallélisme.

Démonstration du multitraitement
Jupyter Notebook permet aisément de vérifier à quel point le multitraitement peut faire gagner
du temps dans les projets de datalogie. Il vous permet en effet d’utiliser la commande magique
%timeit pour le chronométrage. Vous commencez par charger trois éléments : un jeu de
données multiclasse, un algorithme d’apprentissage complexe SVC (Support Vector Classifier)
et une procédure de cross-validation pour obtenir des résultats fiables auprès de toutes les
procédures. Nous reviendrons sur ces différents outils dans la suite du livre. Pour l’instant,
sachez que les procédures deviennent assez intensives, parce que l’approche SVC
produit 10 modèles qu’elle appelle 10 fois avec la cross-validation, ce qui donne 100 modèles
au total.

from sklearn.datasets import load_digits


digits = load_digits()
X, y = digits.data,digits.target
from sklearn.svm import SVC
from sklearn.model_selection import cross_val_score

%timeit single_core = cross_val_score(SVC(), X, y, cv=20, n_jobs=1)

Voici le chronométrage pour un seul cœur (vous avez le temps de faire une pause) :

21.6 s ± 188.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Nous activons ensuite le parallélisme et relançons le chronométrage au moyen de la


commande suivante :

%timeit multi_core = cross_val_score(SVC(), X, y, \


cv=20, n_jobs=-1)

Le chronométrage avec tous les cœurs disponibles montre une amélioration :

13.1 s ± 101 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Notre essai montre un avantage clair, alors que nous n’utilisons qu’un petit jeu de données,
pour lequel Python passe l’essentiel de son temps à démarrer une console puis à exécuter une
partie du code de celle-ci. Cette surcharge représente plusieurs secondes, ce qui constitue une
proportion importante du temps total. Vous imaginez donc le gain que vous pouvez espérer
avec un jeu de données plus volumineux. Le temps d’exécution peut facilement être divisé par
deux ou trois.
Ce code source fonctionne bien dans Jupyter, mais si vous enregistrez dans un script, puis
demandez à Python de l’exécuter depuis une console, ou si vous le chargez dans un
environnement EDI, vous risquez de rencontrer des erreurs à cause des opérations internes à
une tâche multicœur. Comme indiqué plus tôt, la solution consiste à faire venir tout le code
dans la dépendance d’une instruction if qui va permettre de vérifier comment le programme
a été démarré. Voici un exemple de cette solution :

from sklearn.datasets import load_digits


from sklearn.svm import SVC
from sklearn.cross_validation import cross_val_score
if __name__ == ‘__main__’:
digits = load_digits()
X, y = digits.data,digits.target
multi_core = cross_val_score(SVC(), X, y,
cv=20, n_jobs=-1)
Chapitre 13
Analyse de données exploratoire (EDA)
DANS CE CHAPITRE :
» Concept de l’analyse de données exploratoire

» Description des distributions numériques et catégorielles

» Estimation des corrélations et des associations

» Test de différence moyenne dans les groupes

» Visualisation des distributions, des relations et des groupes

L a détecter
datalogie se fonde sur des algorithmes complexes afin de créer des prédictions et de
des signaux significatifs dans le flot de données. Chaque algorithme a des
avantages et des inconvénients. Dans les grandes lignes, le processus est celui-ci : vous
sélectionnez plusieurs algorithmes, vous les faites travailler sur les données, vous réglez leurs
paramètres le plus précisément possible et vous décidez finalement lequel permettra le mieux
de produire le résultat espéré.
Il y a beaucoup d’automatisation dans ce processus, et cela est possible grâce à la puissance
du langage de script Python et aux librairies d’analyse. Les algorithmes d’apprentissage sont
complexes ; les détails de leur manière de travailler peuvent vous sembler obscurs. Même
lorsque vous considérez ces outils comme des sortes de boîtes noires magiques, il ne faut
jamais oublier cet acronyme très simple : GIGO qui signifie Garbage In, Garbage Out (Déchets
en Entrée, Déchets en Sortie, DEDS). Cet axiome est connu depuis toujours en statistiques et
en informatique : quelle que soit la puissance des algorithmes, vous n’obtiendrez de bons
résultats que si des données d’entrée sont de bonne qualité.
L’analyse de données exploratoire EDA (Exploratory Data Analysis) est une approche globale
d’exploration de jeux de données qui utilise des statistiques générales et des visualisations
graphiques pour mieux apprécier la pertinence de ces données. L’analyse EDA rend les étapes
ultérieures d’analyse et de modélisation plus efficaces. Nous allons découvrir au cours de ce
chapitre les descriptions des données indispensables, et verrons en quoi elles peuvent vous
aider à choisir les opérations de transformation de données les plus adéquates pour trouver
vos solutions.
Le fichier contenant le calepin des exemples de ce chapitre correspond à PYDASC_13. Nous
avons expliqué dans l’introduction comment récupérer et décompresser ce fichier archive.

L’approche EDA
L’analyse de type EDA a été conçue dans les laboratoires Bell par le mathématicien et
statisticien John Tukey. Il cherchait à faire apparaître de nouvelles questions et à susciter de
nouvelles actions en étudiant en détail les données, ce qui justifie le terme exploratoire.
Cette approche s’opposait à celle en vigueur à l’époque, qui cherchait plutôt à confirmer des
hypothèses en fin de traitement. Cette stratégie par confirmation cherche à appliquer une
théorie par une procédure ; les données ne servent qu’à faire des tests puis à mettre la théorie
en pratique. La technique d’analyse EDA est apparue à la fin des années 1970, donc bien avant
que se constitue le flot immense des métadonnées (Big Data). Tukey avait deviné que les
activités de test et de modélisation pouvaient facilement être automatisées. Voici ce qu’il avait
dit dans une de ses notes les plus célèbres :
« Le seul moyen pour les humains d’être meilleurs que les ordinateurs consiste à prendre le
risque de faire pire qu’eux. »
Ce genre de phrase vous invite à ne pas vous limiter à l’application de l’algorithme
d’apprentissage automatique, mais à envisager également des actions exploratoires manuelles
avec créativité. Les ordinateurs sont imbattables dès qu’il faut optimiser, mais les humains
restent bien meilleurs pour découvrir, en empruntant des routes surprenantes et en essayant,
apparemment avec peu d’espoir, pour à la fin dégager des solutions très efficaces.
Si vous avez pratiqué les exemples des chapitres précédents, vous avez déjà réalisé quelques
opérations sur les données. L’analyse de données exploratoire EDA est un peu différente : elle
va faire des tests qui vont plus loin que les présupposés élémentaires concernant la qualité des
données, opération qui correspond à l’analyse de données initiales IDA (Initial Data Analysis).
Récapitulons ce que nous avons vu jusqu’à présent :
» Finalisation des observations et marquage des cas manquants.
» Transformation de variables texte ou catégorielles.
» Création de nouvelles caractéristiques grâce aux connaissances du domaine des données.
» Constitution d’un jeu de données numériques dans lequel les lignes sont des observations
et les colonnes des variables ou caractéristiques.

L’analyse EDA va plus loin que l’analyse initiale IDA. C’est un changement d’attitude qui va au-
delà des hypothèses de base. Voici les activités d’une telle exploration :
» description des données ;
» exploration minutieuse des distributions ;
» compréhension des relations entre les variables ;
» repérage des situations inhabituelles ou inattendues ;
» distribution des données dans des groupes ;
» repérage des motifs inhabituels dans les groupes ;
» estimation des différences entre les groupes.

Dans les pages suivantes, nous allons voir en quoi l’analyse EDA vous aide au niveau de la
distribution des variables dans votre jeu. Cette distribution des variables désigne la liste des
valeurs que peut prendre chaque variable en comparaison de leur fréquence d’apparition.
Lorsque vous parvenez à déterminer la distribution d’une variable, vous en savez beaucoup sur
la façon dont elle peut varier et influer sur un algorithme d’apprentissage, ce qui vous aide à
faire en sorte que cet algorithme soit exploité au mieux dans votre projet.

Statistiques descriptives pour données


numériques
La première catégorie d’actions à réaliser avec les données consiste à produire quelques
mesures de synthèse pour faire un premier bilan. Vous allez par exemple rechercher les
valeurs minimale et maximale, puis définir les intervalles avec lesquels vous allez commencer.
Pour mettre en pratique cette exploration, nous allons partir d’un jeu de données simple déjà
rencontré dans les chapitres précédents : le jeu de données des iris de Fisher. Voici comment le
charger depuis le paquetage Scikit-learn :

from sklearn.datasets import load_iris


iris = load_iris()

Ce jeu de test est maintenant chargé dans une variable d’une classe spécifique de Scikit-learn.
Nous pouvons donc produire à partir d’elle un tableau nparray de NumPy et une structure
DataFrame de pandas :

import pandas as pd
import numpy as np

print(‘Voici la version de pandas : %s’ % pd.__version__)


print(‘Voici la version de NumPy : %s’ % np.__version__)
from sklearn.datasets import load_iris
iris = load_iris()
iris_nparray = iris.data

iris_dataframe = pd.DataFrame(iris.data, columns=iris.feature_names)


iris_dataframe[‘group’] = pd.Series([iris.target_names[k] for k in iris.
target], dtype=”category”)

Et voici affichées les versions des librairies :


Voici la version de pandas : 0.23.0
Voici la version de NumPy : 1.14.3

Les librairies NumPy, Scikit-learn et, plus encore, pandas sont en développement constant.
Avant de vous lancer dans les analyses EDA, il est utile d’en vérifier les numéros de versions.
Si vous utilisez une version trop ancienne ou trop récente, les résultats seront différents de
ceux montrés dans ce livre, ou parfois vont échouer. Comparez votre affichage avec celui de
l’exemple précédent. (N.d.T. : S’il y a une différence dans le numéro mineur (sous-version, le
dernier), cela ne porte normalement pas à conséquence.)
Nous allons découvrir dans ce chapitre plusieurs commandes pandas et NumPy, pour explorer
de plus en plus la structure des données. Cette approche progressive donne toute liberté dans
l’analyse, mais il n’est pas inutile de savoir que lorsque vous êtes pressé, vous pouvez obtenir
tout un lot de statistiques en un seul geste par la méthode describe() que vous appliquez à
votre cadre DataFrame. Vous écrirez par exemple print iris_dataframe.describe().

Indicateurs de tendance centrale


Les premières mesures que l’on demande pour des variables numériques dans l’exploration
EDA sont la moyenne et la médiane. Elles permettent d’avoir une première idée de la
centralisation des données et de leur symétrie.
La librairie pandas vous laisse obtenir facilement ces deux mesures. Voici comment obtenir la
moyenne pour la structure DataFrame de notre collection d’iris :

print(iris_dataframe.mean(numeric_only=True))

Voici le résultat affiché :

sepal length (cm) 5.843333


sepal width (cm) 3.054000
petal length (cm) 3.758667
petal width (cm) 1.198667
dtype: float64

De même, voici comment obtenir la médiane du même jeu :

print(iris_dataframe.median(numeric_only=True))

Vous obtenez ainsi la médiane pour toutes vos variables :

sepal length (cm) 5.80


sepal width (cm) 3.00
petal length (cm) 4.35
petal width (cm) 1.30
dtype: float64

Cette valeur médiane permet de localiser la position de la séparation entre les deux moitiés de
valeurs. La médiane est moins influencée par les cas anormaux ou par une distribution
déséquilibrée des valeurs autour de la moyenne. Notez que dans l’exemple, les moyennes ne
sont pas centrées (aucune variable n’a de valeur nulle) et que la médiane des longueurs de
pétales est assez différente de la moyenne correspondante. Cela invite à pousser
l’investigation.
Voici ce qu’il faut vérifier au niveau des mesures de tendance centrale :
» Est-ce qu’il y a des moyennes égales à zéro ?
» Est-ce qu’elles sont différentes les unes des autres ?
» Est-ce que la médiane est différente de la moyenne correspondante ?

Variance, écart type et étendue


Il s’agit ensuite de mesurer la variance ou plutôt sa racine carrée qui correspond à l’écart type
(standard deviation). L’écart type donne autant d’informations que la variance, mais il est plus
facile à comparer à la moyenne parce que l’unité de mesure est la même. La variance constitue
un bon indicateur de l’intérêt d’une moyenne pour connaître la distribution des variables ; en
effet, elle informe sur la façon dont les valeurs de la variable sont distribuées autour de la
moyenne. Plus la variance est élevée, plus loin peuvent se trouver certaines valeurs par
rapport à cette moyenne. Un exemple :

print(iris_dataframe.std())

Voici le résultat affiché pour chacune des quatre variables :

sepal length (cm) 0.828066


sepal width (cm) 0.433594
petal length (cm) 1.764420
petal width (cm) 0.763161
dtype: float64

Il y a lieu ensuite de mesurer l’étendue (range) qui est tout simplement la différence entre la
valeur minimale et la valeur maximale pour chaque variable quantitative. L’étendue permet
d’en apprendre plus au sujet des différences d’échelle entre les variables :

print(iris_dataframe.max(numeric_only=True)
- iris_dataframe.min(numeric_only=True))

Voici ce qu’affiche l’instruction précédente :

sepal length (cm) 3.6


sepal width (cm) 2.4
petal length (cm) 5.9
petal width (cm) 2.4
dtype: float64

Vous pouvez ensuite confronter vos mesures d’écart type et d’étendue aux moyennes et
médianes. Si un écart type ou une étendue semble trop élevé par rapport à vos mesures de
centralité, c’est peut-être l’indice qu’il y a un problème. Peut-être existe-t-il des valeurs très
inhabituelles qui perturbent les calculs ou une distribution inattendue des valeurs autour de la
moyenne.

Utilisation des centiles


La médiane permet de connaître la position centrale de la distribution de valeurs, mais
d’autres positions peuvent nous intéresser en dehors du minimum et du maximum. Par
exemple, la position à 25 % (le quartile inférieur) et celle à 75 % (le quartile supérieur) aident
à déterminer la distribution. Elles constituent les données d’entrée des diagrammes de type
boîte à moustaches que nous verrons dans la section sur la visualisation un peu plus loin dans
ce chapitre. Produisons nos quartiles :

print(iris_dataframe.quantile([0,.25,.50,.75,1]))

La Figure 13.1 montre le résultat qui apparaît dans un panneau inférieur de votre fenêtre. Les
lignes du jeu de données sont réparties entre les différents quartiles, avec les variables dans
les colonnes. Le quartile des 25 % de la longueur des sépales vaut 5.1 ; cela signifie que 25 %
des valeurs du jeu de données pour cette mesure sont inférieures à 5.1.

Figure 13.1 : Comparaison de données avec les quartiles.

La différence entre les quartiles supérieur et inférieur correspond à l’écart interquartiles


(IQR). Cette mesure permet d’estimer l’échelle des variables les plus intéressantes. Vous
n’avez pas besoin de calculer cet écart, mais il vous sera accessible directement dans les boîtes
à moustaches. Il aide à connaître les limites plausibles de la distribution. Les valeurs situées
sous le quartier inférieur et jusqu’au minimum, et celles situées au-dessus du quartier
supérieur sont très rares, mais peuvent impacter négativement les résultats. Ces cas rares sont
des aberrants, et nous leur consacrons le Chapitre 16.
Définitions des paramètres de forme (normalité)
Le dernier couple de mesures concernant les variables numériques concerne le coefficient
d’asymétrie et celui d’acuité ou kurtosis :
» Asymétrie (skewness) : elle est exprimée par rapport à la moyenne. Si elle est négative,
la branche gauche est trop longue et l’essentiel des observations se situe du côté droit de
distribution. Si elle est positive, c’est le contraire.
» Kurtosis (ou acuité) : elle permet de voir si les pics et creux de la distribution ont la
forme appropriée. Si la valeur est supérieure à zéro, c’est que la distribution comporte un
pic marqué. Si elle est inférieure à zéro, la distribution est trop plate.

Ces deux mesures vous aident à estimer la forme des données et de réaliser un test formel
pour localiser les variables qui mériteraient quelques ajustements ou transformations, afin de
s’approcher mieux d’une distribution de type gaussien. N’oubliez pas que vous allez visualiser
les données par la suite ; ce n’est ici que la première étape d’un processus assez long.
La distribution normale ou gaussienne est la plus utilisée en statistiques, de par ses propriétés
mathématiques spéciales. Elle représente la pierre angulaire de nombreux tests et modèles
statistiques. Certains d’entre eux, comme la régression linéaire, sont très utilisés en datalogie.
Dans une distribution gaussienne, la moyenne et la médiane ont la même valeur et les valeurs
sont distribuées de façon symétrique autour de la moyenne, ce qui donne un profil en cloche ;
l’écart type désigne la distance depuis la moyenne et le point où la courbe de distribution
change de concave en convexe (point d’inflexion). Ces paramètres font de la distribution
gaussienne une distribution remarquable qui est mise à profit dans les calculs statistiques.
Dans les données réelles, vous rencontrerez rarement une distribution gaussienne, aussi
importante soit-elle de par ses propriétés statistiques. Vous devrez faire face à des
distributions très différentes. C’est pour cette raison qu’il faut faire une analyse exploratoire
EDA et réaliser les mesures d’asymétrie et de kurtosis.
Dans un exemple antérieur du chapitre qui mesurait la variance, nous avions remarqué que la
longueur des pétales montrait une différence entre moyenne et médiane. Nous reprenons le
même exemple pour évaluer l’asymétrie et l’acuité afin de savoir s’il faut intervenir sur la
variable.
Pour réaliser ces deux mesures, il s’agit de déterminer si la p-valeur est inférieure ou égale
à 0.05. Si c’est le cas, cela vous oblige à rejeter la normalité (votre variable distribuée en tant
que distribution gaussienne). Il en découle que vous pourrez obtenir de meilleurs résultats en
tentant de transformer la variable en distribution normale. Voici comment réaliser le test
concerné :

from scipy.stats import kurtosis, kurtosistest


variable = iris_dataframe[‘petal length (cm)’]
k = kurtosis(variable)
zscore, pvalue = kurtosistest(variable)
print(‘Acuité (Kurtosis) %0.3f z-score %0.3f p-value %0.3f’
% (k, zscore, pvalue))

Voici le score d’acuité que l’on obtient :

Acuité (Kurtosis) -1.395 z-score -14.811 p-value 0.000

Et voici le test pour calculer l’asymétrie :

from scipy.stats import skew, skewtest


variable = iris_dataframe[‘petal length (cm)’]
s = skew(variable)
zscore, pvalue = skewtest(variable)
print(‘Asymétrie (Skewness) %0.3f z-score %0.3f p-value %0.3f’
% (s, zscore, pvalue))

Voici son résultat :

Asymétrie (Skewness) -0.272 z-score -1.398 p-value 0.162

Les résultats nous apprennent que les données sont légèrement asymétriques vers la gauche,
sans l’être assez pour les rendre inexploitables. Le vrai problème est que la courbe est bien
trop plate pour ressembler à une cloche, ce qui nous oblige à pousser plus loin.
Il est d’usage de tester automatiquement l’asymétrie et l’acuité de toute variable. Cela vous
permet ensuite d’inspecter celles dont les valeurs sont les plus fortes au simple regard. La
normalité d’une distribution peut cacher différents soucis, par exemple, l’existence de données
aberrantes par rapport au groupe, ce qui n’est possible de voir qu’en produisant un
diagramme.

Mesure des données catégorielles


Le jeu de données Iris comporte quatre variables métriques avec un résultat qualitatif. Vous
pouvez prendre des mesures de description des variables métriques avec les moyennes et les
variances ; pour des variables qualitatives, vous travaillez avec des fréquences.
Nous partons dans ce jeu de mesures de longueur et de largeur en centimètres à partir
desquelles nous devons maintenant produire des mesures qualitatives en distribuant ces
mesures dans différents groupes à intervalles choisis. Le paquetage pandas offre les deux
fonctions cut() et qcut() pour transformer une variable métrique en variable qualitative :
» cut() doit recevoir en entrée une série de valeurs charnières pour distribuer les mesures
ou bien un nombre entier de groupes pour distribuer les variables en bacs de largeur
unique.
» qcut() attend en entrée une série de centiles pour distribuer la variable.

Pour produire un nouveau cadre DataFrame catégoriel, nous pouvons concaténer un


regroupement de bacs (bins) avec la commande suivante pour chaque variable (revoyez si
nécessaire la section sur le regroupement/binning dans le Chapitre 9) :

pcts = [0, .25, .5, .75, 1]


iris_binned = pd.concat(
[pd.qcut(iris_dataframe.iloc[:,0], pcts, precision=1),
pd.qcut(iris_dataframe.iloc[:,1], pcts, precision=1),
pd.qcut(iris_dataframe.iloc[:,2], pcts, precision=1),
pd.qcut(iris_dataframe.iloc[:,3], pcts, precision=1)],
join=’outer’, axis = 1)

Cet exemple utilise le regroupement, binning. Il permet aussi d’explorer la situation dans
laquelle une variable est située sous ou au-dessus d’une charnière, qui est en général la
moyenne ou la médiane. Dans ce cas, vous réglez la fonction pd.qcut() au centile 0.5 ou
pd.cut() à la valeur moyenne de la variable.
Le regroupement transforme des variables numériques en variables catégorielles, afin
d’améliorer votre compréhension des données ainsi que la phase d’apprentissage qui suit en
réduisant le bruit causé par les valeurs aberrantes et les non-linéarités de la variable
transformée.

Utilisation des fréquences


Vous pouvez obtenir une fréquence pour chacune des variables catégorielles du jeu, aussi bien
pour les variables prédictives que pour le résultat. Le code suivant le permet :

print(iris_dataframe[‘group’].value_counts())

Dans cet exemple, les fréquences affichées montrent que tous les groupes ont la même taille :

virginica 50
versicolor 50
setosa 50
Name: group, dtype: int64

Vous pouvez même demander le calcul des fréquences, par exemple pour la longueur des
pétales regroupés suite à l’action précédente :

print(iris_binned[‘petal length (cm)’].value_counts())

Dans ce cas, le regroupement produit des groupes différents :

(0.9, 1.6] 44
(4.4, 5.1] 41
(5.1, 6.9] 34
(1.6, 4.4] 31
Name: petal length (cm), dtype: int64

Nous pouvons également extraire des informations de base sur les fréquences, et notamment
le nombre de valeurs uniques pour chaque variable et le mode de la fréquence (qui
correspondent aux lignes top et freq dans le résultat).

print(iris_binned.describe())

Le résultat est visible dans la Figure 13.2.

Figure 13.2 : Statistiques descriptives du regroupement.

L’obtention des fréquences permet d’accéder à quelques points intéressants des


caractéristiques qualitatives :
» le mode de la distribution de fréquences qui correspond à la catégorie la plus fréquente ;
» les autres catégories les plus fréquentes, en particulier lorsqu’elles sont comparables au
mode (distribution bimodale) ou au contraire lorsqu’il y a une énorme différence entre les
deux ;
» la distribution des fréquences entre catégories, s’il y a décroissance rapide ou distribution
équilibrée ;
» les catégories rares.

Création de tableaux de contingence


Pour rendre visibles les relations entre plusieurs variables qualitatives, vous pouvez réunir
plusieurs distributions de fréquences catégorielles. La fonction, nommée pandas.crosstab()
permet justement d’associer des variables ou des groupes de variables, ce qui peut aider à
déduire des structures dans les données ou des relations.
Dans l’exemple qui suit, nous allons estimer les relations entre la variable de sortie et les
longueurs de pétales. Nous verrons ainsi que certains résultats n’apparaissent jamais en même
temps que certaines classes ou catégories de pétales. La Figure 13.3 présente les différents
types d’iris du côté gauche du résultat suivis de la sortie au niveau de la longueur des pétales.

print(pd.crosstab(iris_dataframe[‘group’],
iris_binned[‘petal length (cm)’]))

Figure 13.3 : Table de contingence basée sur les groupes et le regroupement.

Visualisation d’analyse exploratoire


Pour l’instant, nous n’avons fait qu’explorer les variables une par une. En termes techniques, si
vous avez pratiqué les exemples, vous avez obtenu une description univariée des données en
ne nous focalisant que sur leurs variations indépendantes. Mais les données d’entrée sont plus
riches en informations que celles que présente une seule variable ; elles peuvent montrer des
interactions dans les variations de plusieurs variables. Pour utiliser un plus grand nombre de
données, il faut se lancer dans une exploration bivariée pour voir comment des couples de
variables se comportent. En partant de cette exploration, vous pourrez aller encore plus loin en
adoptant l’approche multivariée, consistant à prendre en compte toutes les relations entre les
variables.
L’approche univariée ne s’intéresse qu’à un nombre limité de statistiques descriptives. En
combinant plusieurs variables au même groupe de variables, vous augmentez le nombre de
possibilités. Mais ce genre d’exploration va inonder le datalogue de test et d’analyses
bivariées. Vous éviterez la noyade en réalisant des visualisations pour limiter rapidement vos
tests et analyses aux seuls indices et motifs remarquables. Avec peu d’informations
graphiques, une visualisation peut rendre visible plus aisément la grande variété des traits
caractéristiques des variables et leurs relations réciproques.

Inspection de boîtes à moustaches


Une boîte à moustaches permet de visualiser une distribution et ses étendues extrêmes, ce qui
permet de voir si certaines observations sont vraiment trop éloignées de la majorité des autres,
situation problématique pour certains algorithmes d’apprentissage. L’exemple suivant crée une
boîte à moustaches simple à partir du jeu de données Iris :

boxplots = iris_dataframe.boxplot(fontsize=9)

La Figure 13.4 montre la structure fondamentale de la solution de chacune des variables, par
la position des deux centiles 25 et 75 qui correspondent aux extrémités de la boîte. Vous y
voyez également la médiane au centre de la boîte. Les segments de droite qui forment les
moustaches représentent 1,5 fois l’écart interquartile IQR par rapport aux extrémités de la
boîte (ou par rapport à la distance à la valeur extrême si elle reste dans la limite de 1,5 fois
l’écart IQR). Les valeurs inhabituelles sont toutes visibles en tant que signes au-delà des
moustaches.

Figure 13.4 : Boîte à moustaches organisée selon les variables.

Ce genre de boîte permet facilement de détecter les différences entre groupes. La


Figure 13.5 laisse deviner que les trois variétés de fleurs (setosa, versicolor et virginica) n’ont
pas les mêmes longueurs de pétales. Seules deux variétés se chevauchent légèrement (les
deuxième et troisième).

import matplotlib.pyplot as plt


boxplots = iris_dataframe.boxplot(column=’petal length (cm)’,
by=’group’, fontsize=10)
plt.suptitle(“”)
plt.show()

Figure 13.5 : Boîte à moustaches des longueurs de pétales présentées par groupe.
Réalisation d’un test T
Vous pouvez obtenir une vérification statistique de ce que signifie la différence entre les
moyennes de plusieurs groupes une fois que vous avez détecté une différence d’un groupe par
rapport à une variable. Pour ce faire, vous pouvez réaliser un test T (approprié lorsque la
population échantillonnée montre une distribution normale exacte) ou une analyse de variance
à un seul facteur (ANOVA).

from scipy.stats import ttest_ind

group0 = iris_dataframe[‘group’] == ‘setosa’


group1 = iris_dataframe[‘group’] == ‘versicolor’
group2 = iris_dataframe[‘group’] == ‘virginica’
variable = iris_dataframe[‘petal length (cm)’]

print(‘var1 %0.3f var2 %03f’ % (variable[group1].var(),


variable[group2].var()))

Le test T va comparer deux groupes en même temps ; c’est à vous de décider si les groupes ont
ou pas une variance similaire. Cela suppose de calculer d’abord cette variance, comme ceci :

variable = iris_dataframe[‘sepal width (cm)’]


t, pvalue = ttest_ind(variable[group1], variable[group2],
axis=0, equal_var=False)
print(‘t statistic %0.3f p-value %0.3f’ % (t, pvalue))

La statistique t et la p-valeur correspondantes sont les suivantes :

t statistic -3.206 p-value 0.002

La p-valeur correspond à la probabilité que la différence statistique t qui a été calculée ne soit
que le fruit du hasard. En général, lorsque la valeur est inférieure à 0.05, vous pouvez
confirmer que les moyennes de groupes sont suffisamment différentes.
Vous pouvez tester plus de deux groupes au moyen d’une analyse de variance à un seul facteur
ANOVA unidirectionnelle. Dans ce cas, la p-valeur s’interprète de façon proche au test T :

from scipy.stats import f_oneway


variable = iris_dataframe[‘sepal width (cm)’]
f, pvalue = f_oneway(variable[group0],
variable[group1],
variable[group2])
print(‘One-way ANOVA F-value %0.3f p-value %0.3f’
% (f,pvalue))

Voici le résultat de l’analyse de variance ANOVA :

One-way ANOVA F-value 47.364 p-value 0.000

Observation de coordonnées parallèles


Les coordonnées parallèles permettent de repérer les groupes du résultat qui sont faciles à
séparer des autres. Ce genre de tracé est véritablement multivarié, puisqu’il permet de
représenter toutes les données en même temps. Voici comment vous servir de ces coordonnées
parallèles :

from pandas.plotting import parallel_coordinates


iris_dataframe[‘group’] = iris.target
iris_dataframe[‘labels’] = [iris.target_names[k]
for k in iris_dataframe[‘group’]]
pll = parallel_coordinates(iris_dataframe, ‘labels’)

Dans la Figure 13.6, l’axe des abscisses montre toutes les variables quantitatives alignées.
L’axe des ordonnées montre les observations sous forme de lignes parallèles de couleurs
différentes en fonction du groupe propriétaire.
Figure 13.6 : Les coordonnées parallèles aident à déduire qu’un groupe est facile à séparer des autres.

Lorsque les lignes parallèles d’un groupe convergent toutes à l’écart des autres, c’est un
groupe aisément séparable. Cette visualisation permet également d’estimer la possibilité pour
certaines caractéristiques de discriminer les groupes.

Visualisation d’une distribution


Un diagramme en courbes ou un histogramme permet de représenter l’information des
statistiques descriptives et des boîtes à moustaches. Vous obtenez ainsi une vision globale de la
distribution des valeurs. La Figure 13.7 montre toutes les distributions du jeu de données. Vous
devinez immédiatement certains profils et échelles particuliers, par exemple le fait que les
deux caractéristiques des pétales comportent deux crêtes.

cols = iris_dataframe.columns[:4]
densityplot = iris_dataframe[cols].plot(kind=’density’)

Un histogramme donne un autre aperçu des distributions de façon plus détaillée :

variable = iris_dataframe[‘petal length (cm)’]


single_distribution = variable.plot(kind=’hist’)

L’histogramme de la Figure 13.8 concerne la longueur des pétales. Vous remarquez


immédiatement un trou de distribution qui peut s’avérer être une découverte prometteuse si
vous parvenez à le mettre en relation avec un des groupes de fleurs.

Figure 13.7 : Distribution des caractéristiques et densités.

Figure 13.8 : Distribution plus détaillée dans un histogramme.

Diagrammes en nuage de points


Avec un diagramme en nuage de points, les deux variables à comparer fournissent les
coordonnées pour dessiner les observations sous forme de points dans le plan. Vous obtenez
ainsi un nuage de points. En étirant ce nuage pour qu’il s’allonge, vous pouvez vérifier que les
variables sont corrélées. Voici une démonstration de ce principe :

palette = {0: ‘red’, 1: ‘yellow’, 2:’blue’}


colors = [palette[c] for c in iris_dataframe[‘group’]]
simple_scatterplot = iris_dataframe.plot(
kind=’scatter’, x=’petal length (cm)’,
y=’petal width (cm)’, c=colors)

Le diagramme est représenté en Figure 13.9. Il compare les longueurs et les largeurs des
pétales. Nous distinguons plusieurs groupes avec des couleurs différentes. La forme étirée de
la distribution montre une forte corrélation entre les deux variables observées. La répartition
du nuage en groupes laisse espérer la possibilité de les séparer.

Figure 13.9 : Diagramme en nuage révélant les relations entre deux variables.

Lorsque le nombre de variables n’est pas trop important, comme ici, vous pouvez même
générer automatiquement les points pour toutes les combinaisons de variables. Vous obtenez
ainsi une matrice de nuages. L’exemple suivant montre comment créer ce genre de
diagramme :

from pandas.plotting import scatter_matrix


palette = {0: “red”, 1: “yellow”, 2: “blue”}
colors = [palette[c] for c in iris_dataframe[‘group’]]
matrix_of_scatterplots = scatter_matrix(
iris_dataframe, figsize=(6, 6),
color=colors, diagonal=’kde’)

La Figure 13.10 montre le résultat pour le jeu de données Iris. La diagonale qui représente
l’estimation de densité peut être remplacée par un histogramme en fournissant le paramètre
diagonal=hist.

Figure 13.10 : Une matrice de diagramme en nuage affichant plusieurs informations à la fois.

Principe de la corrélation
Nous pouvons représenter les relations entre les variables de façon graphique, mais également
en réalisant des estimations statistiques. Lorsque vous traitez des variables numériques,
l’estimation est une corrélation, la plus fameuse étant la corrélation de Pearson. Elle constitue
le fondement des modèles d’estimation linéaire complexes. Lorsque vous travaillez avec des
variables catégorielles, votre estimation est une association, et l’outil le plus utilisé pour
mesurer des associations entre caractéristiques est la statistique du Khi 2 (chi-square).

Covariance et corrélation
La première mesure des relations entre deux variables est la covariance. Elle permet de
savoir si les deux variables se comportent de la même façon par rapport à leur moyenne.
L’association est dite positive lorsque les valeurs isolées des deux variables sont la plupart au-
dessus ou la plupart en dessous de la moyenne correspondante. Les deux tendent donc à
converger et vous pouvez prédire le comportement de l’une à partir de l’autre. La covariance
est dans ce cas une valeur positive et plus elle est importante, plus elle est forte.
En revanche, si une variable est en général au-dessus et l’autre en général au-dessous de la
moyenne correspondante, l’association est négative. Même si cela signe une divergence, la
situation permet de faire des prédictions. En observant l’état de l’une, vous pouvez deviner
l’état probable de l’autre, bien qu’elles soient en sens opposé. La covariance est dans ce cas
une valeur négative.
La troisième possibilité correspond au cas où les deux variables divergent ou convergent de
temps à autre. La covariance va tendre vers zéro, ce qui prouve que les variables ne partagent
que peu de causes et ont donc des comportements indépendants.
En théorie, si votre variable cible est numérique, vous voudrez qu’elle soit fortement
covariante (positivement ou négativement) par rapport aux variables prédictives. En cas de
forte covariance positive ou négative entre les variables prédictives, cela signe une redondance
d’informations, c’est-à-dire que les variables désignent les mêmes données et ne font que
transmettre la même signification en termes légèrement différents.
Vous pouvez facilement calculer une matrice de covariance au moyen de cov() de la librairie
pandas. Appliquons-la immédiatement à la structure DataFrame du jeu de données Iris :

iris_dataframe.cov()

La Figure 13.11 montre une matrice avec des variables présentes dans les lignes et dans les
colonnes. Cette observation, parmi plusieurs combinaisons de lignes et de colonnes, permet de
connaître le niveau de covariance entre les variables choisies. En lisant ces résultats, vous
déduisez immédiatement qu’il y a peu de relations entre les longueurs et les épaisseurs des
sépales, ce qui prouve que ce sont des informations différentes. Il semble en revanche y avoir
une relation particulière entre les longueurs et les épaisseurs des pétales ; l’exemple ne
permet pas d’en savoir plus sur cette relation parce que la mesure n’est pas facile à
interpréter.

Figure 13.11 : Matrice de covariance pour le jeu Iris.

Vous devez utiliser une mesure standard, mais différente, car les échelles des variables
observées ont un impact sur la covariance. Pour y parvenir, vous adoptez la corrélation, qui est
une estimation de la covariance après standardisation des variables. Voici un calcul de
corrélation au moyen de la méthode corr() de pandas :

iris_dataframe.corr()

Voici la matrice de corrélation résultante (Figure 13.12) :


Figure 13.12 : Matrice de corrélation pour le jeu Iris.

Les choses deviennent encore plus intéressantes, puisque les valeurs de corrélation sont
bornées entre -1 et +1. La relation entre longueur et épaisseur de pétales est positive, puisque
la valeur 0.96 est proche du maximum.
Vous pouvez faire calculer les matrices de covariance et de corrélation au moyen de certaines
commandes de NumPy :

covariance_matrix = np.cov(iris_nparray, rowvar=0)


correlation_matrix = np.corrcoef(iris_nparray, rowvar=0)

Dans le domaine des statistiques, ce genre de corrélation est une corrélation de Pearson ; son
coefficient est le r de Pearson ou PCC.

Une autre technique intéressante consiste à élever la corrélation au carré. Cela fait perdre le
signe de la relation et la nouvelle valeur permet de connaître le pourcentage d’informations
partagées entre deux variables. Dans notre exemple, une valeur de 0.96 signifie qu’il y a 96 %
d’informations partagées. Pour obtenir une matrice de corrélation au carré de l’exemple, nous
utiliserions l’instruction suivante :

iris_dataframe.corr)**2

Rappelons que la covariance et la corrélation sont toutes deux basées sur les moyennes ; elles
ont donc tendance à incarner des relations qui peuvent être exprimées au moyen de deux
formulations linéaires. Dans les jeux de données réels, les variables montrent rarement une
telle formulation linéaire, et sont plutôt très non linéaires, avec des courbes et des
rebroussements. Heureusement, vous pouvez rendre ces relations linéaires en appliquant des
transformations mathématiques. Souvenez-vous qu’il ne faut utiliser les corrélations que pour
étudier les relations entre les variables, pas pour en exclure une partie.

Corrélations non paramétriques


Les corrélations conviennent bien pour des variables numériques avec des relations
strictement linéaires, mais dans certains cas, vos caractéristiques seront ordinales
(numériques avec un certain ordre) ou bien vous pressentez une certaine non-linéarité en
constatant une distribution des données non normale. Une solution consiste à tester les
corrélations douteuses en réalisant une corrélation non paramétrique, par exemple une
corrélation de Spearman triée selon les rangs (grâce à des exigences moindres en termes de
distribution des variables considérées). Une corrélation de Spearman va transformer les
valeurs numériques en positions de classement puis corréler les classements, ce qui limite
l’influence des relations non linéaires entre les deux variables étudiées. Le résultat corrélé, en
général désigné sous le nom de lettre grecque rho, doit s’interpréter de la même façon que la
corrélation de Pearson.
En guise d’exemple, vérifions la relation entre les longueurs et les largeurs des sépales, la
corrélation de Pearson s’étant montrée assez faible :

from scipy.stats import spearmanr


from scipy.stats.stats import pearsonr
a = iris_dataframe[‘sepal length (cm)’]
b = iris_dataframe[‘sepal width (cm)’]
rho_coef, rho_p = spearmanr(a, b)
r_coef, r_p = pearsonr(a, b)
print(‘r de Pearson %0.3f ¦ rho de Spearman %0.3f’
% (r_coef, rho_coef))

Voici le résultat des deux corrélations comparées :

r de Pearson -0.109 ¦ rho de Spearman -0.159


Cet exemple confirme une association faible entre les deux variables au moyen du test non
paramétrique.

Test du Khi 2 pour les tables


Un autre test non paramétrique est disponible pour tester les relations si vous travaillez avec
des tableaux croisés. Le test convient aux variables numériques et catégorielles (après
regroupement en bacs bins). Il s’agit de la statistique du Khi carré ; elle permet de savoir si la
distribution dans les tables de deux variables est comparable de façon statistique à une table
dans laquelle il a été émis l’hypothèse que les deux variables n’ont aucune relation entre elles
(hypothèse d’indépendance). Voici un exemple d’application de cette technique :

from scipy.stats import chi2_contingency


table = pd.crosstab(iris_dataframe[‘group’],
iris_binned[‘petal length (cm)’])
chi2, p, dof, expected = chi2_contingency(table.values)
print(‘Chi-square %0.2f p-value %0.3f’ % (chi2, p))

La statistique Khi carré qui en résulte est celle-ci :

Khi carré chi-square 212.43 p-value 0.000

La p-valeur indique la probabilité que la différence de Khi carré soit due au simple hasard. La
valeur élevée pour Khi carré et la valeur remarquable pour la p-valeur nous confirment que la
variable de longueur de pétale permet effectivement de distinguer les différents groupes d’iris.
Plus la valeur du Khi carré est élevée, plus il est probable que les deux variables soient liées.
Cependant, cette mesure est dépendante du nombre de cellules dans la table. Ne l’utilisez pas
pour comparer des tests de Khi carré différents, sauf si vous êtes certain que les tables ainsi
confrontées ont la même forme.
La loi du Khi carré est particulièrement intéressante pour évaluer les relations entre les
variables numériques après regroupement, même si une importante non-linéarité risque de
perturber le calcul du r de Pearson. En effet, et à la différence des mesures de corrélation,
vous pouvez ainsi être informé d’une association éventuelle, sans pour autant obtenir des
détails quant à la direction ou à la magnitude absolue.

Modification d’une distribution de données


Voici quelques autres opérations qu’il est possible de réaliser pendant la phase d’analyse de
données exploratoire EDA :
» production de nouvelles caractéristiques par combinaison de variables distinctes mais
liées ;
» détection de groupes masqués et de valeurs étranges dans les données ;
» essai de plusieurs modifications intéressantes de la distribution par regroupement binning
(ou autre discrétisation comme avec des variables binaires).

Pendant votre analyse exploratoire, vous ne devez pas négliger l’importance des
transformations de données dans la préparation de la phase d’apprentissage. Cela signifie qu’il
faudra appliquer quelques formules mathématiques. La plupart des algorithmes
d’apprentissage fonctionnent au mieux lorsque le coefficient corrélation de Pearson est
maximal entre les variables à prédire et celles servant à la prédiction. Dans la section suivante,
nous allons découvrir les procédures les plus usitées qui permettent d’enrichir les relations
entre variables. La transformation que vous allez choisir va dépendre de la distribution réelle
des données. Vous ne pouvez donc pas le décider au départ. La découverte se fera pendant
l’analyse exploratoire et de nombreux tests. Dans cette section, nous tenons également à
souligner la nécessité de faire correspondre le processus de transformation à la formule
mathématique adoptée.

Distributions statistiques différentes


Vous aurez affaire à une vaste gamme de distributions différentes pendant votre pratique de
datalogie. Certaines d’entre elles tirent leur nom du monde des probabilités, mais pas toutes.
Certaines peuvent valablement être considérées comme se comportant comme des
distributions normales, mais pas d’autres. Un problème peut surgir, selon l’algorithme choisi
pour le processus d’apprentissage. Voici une règle générale : si votre modèle est une
régression linéaire ou s’il fait partie de la famille des modèles linéaires parce qu’il revient à
une somme de coefficients, vous aurez intérêt à considérer la standardisation des variables
ainsi que la transformation de distribution.
En dehors des modèles linéaires, il existe de nombreux autres algorithmes d’apprentissage qui
sont réellement indifférents à la distribution des variables utilisées. Cela dit, vous pouvez
toujours espérer un effet positif en transformant les variables de votre jeu pour que la
distribution prenne un profil plus gaussien.

Standardisation Z-score (cote Z)


Au cours de votre exploration, vous aurez peut-être remarqué que vos différentes variables
n’ont pas les mêmes échelles et que leurs distributions sont hétérogènes. Vous devez donc en
conséquence transformer les variables pour les rendre aisément comparables :

from sklearn.preprocessing import scale


variable = iris_dataframe[‘sepal width (cm)’]
stand_sepal_width = scale(variable)

Certains des algorithmes vont adopter un comportement surprenant si vous ne remettez pas
vos variables à l’échelle par une standardisation. En guise de règle, soyez toujours vigilant
avec les modèles linéaires, les analyses de groupes clusters et tout algorithme qui prétend se
fonder sur des mesures statistiques.

Transformation d’autres distributions usitées


Vous pourriez être déçu en étudiant la corrélation de variables ayant une forte asymétrie et un
fort Kurtosis. Nous avons vu plus haut dans ce chapitre, qu’utiliser une mesure de corrélation
non paramétrique, comme le coefficient Spearman, permet d’en apprendre plus au sujet de
deux variables qu’avec le coefficient r de Pearson. Dans ce cas, transformez votre trouvaille en
une nouvelle caractéristique transformée, comme ceci :

from scipy.stats.stats import pearsonr


tranformations = {‘x’: lambda x: x,
‘1/x’: lambda x: 1/x,
‘x**2’: lambda x: x**2,
‘x**3’: lambda x: x**3,
‘log(x)’: lambda x: np.log(x)}
a = iris_dataframe[‘sepal length (cm)’]
b = iris_dataframe[‘sepal width (cm)’]
for transformation in tranformations:
b_transformed = tranformations[transformation](b)
pearsonr_coef, pearsonr_p = pearsonr(a, b_transformed)
print(‘Transformation: %s \t r de Pearson: %0.3f’
% (transformation, pearsonr_coef))

Voici le résultat de cet exemple :

Transformation: x r de Pearson: -0.109


Transformation: 1/x r de Pearson: 0.073
Transformation: x**2 r de Pearson: -0.122
Transformation: x**3 r de Pearson: -0.131
Transformation: log(x) r de Pearson: -0.093

Lors de l’exploration des différentes transformations possibles, une boucle de répétition for
pourrait montrer qu’une transformation de puissance augmenterait la corrélation entre deux
variables. Ceci augmenterait les performances d’un algorithme d’apprentissage linéaire. Vous
pouvez même tenter d’autres transformations (racine carrée np.sqrt(x) et exponentielle
np.exp(x)), voire combiner des transformations, comme avec le log inverse np.log(1 : x).
Dans certains cas, le fait de transformer une variable par une action logarithmique va poser
problème parce que cela ne fonctionne pas avec les valeurs négatives et nulles. Vous devrez
d’abord rééchelonner les valeurs pour que la plus petite soit égale à 1. Vous y arrivez aisément
en combinant quelques fonctions du paquetage NumPy :
np.log(x + np.abs(np.min(x)) + 1)
Chapitre 14
Réduction de dimensionnalité
DANS CE CHAPITRE :
» La magie de la décomposition en valeurs singulières

» Différence entre les facteurs et les composantes

» Récupération et association automatique des images et des textes

» Création d’un système de recommandation de films

e que ce que l’on appelle Big Data (métadonnées) est une collection de jeux de données
C tellement volumineuse que ces données deviennent difficiles à traiter avec les techniques
habituelles. Les problèmes de statistiques exploitent de petites quantités d’échantillons. Vous
utilisez des techniques de statistiques traditionnelles pour les petits volumes et des techniques
de datalogie pour les gros volumes.
L’énormité peut désigner l’existence d’un très grand nombre d’exemples, d’observations (de
lignes). C’est dans ce sens que l’on pense tout d’abord aux métadonnées. Il est par exemple
assez difficile d’exploiter une base de données contenant des millions de clients en appliquant
systématiquement les mêmes traitements à la totalité de la base. Cette « grande longueur » n’est
pourtant pas la seule manière de caractériser un gisement de mégadonnées. Les données
peuvent en effet être volumineuses dans le sens horizontal, c’est-à-dire par le nombre de cas ou
caractéristiques (de colonnes). On parle dans ce cas de dimensionnalité des données. Lorsqu’une
base comporte beaucoup de colonnes, beaucoup de variables, des centaines de milliers, un vrai
problème se pose. Même si vous ne pensez traiter que quelques cas à la fois, l’obligation de
gérer un très grand nombre de caractéristiques peut rendre les analyses impossibles.
Cette grande quantité de dimensions oblige à chercher des techniques pour filtrer les
informations, en ne conservant que celles qui peuvent le mieux aider à résoudre le problème. Ce
genre de filtre va réduire les dimensions en supprimant d’abord toutes les informations
redondantes. Dans ce chapitre, nous allons voir comment réduire les dimensions en détectant les
répétitions d’information. Ce travail peut être comparé à une compression d’informations, un
peu comme la compression des fichiers sur disque dur pour économiser l’espace de stockage.
N.d.T. : Le même genre d’épuration sémantique est appliqué dans le processus de normalisation
lors de la conception des structures des bases de données relationnelles (formes normales,
https://fr.wikipedia.org/wiki/Forme_normale_(bases_de_donn%C3%A9es_relationnelles)).

Le fichier calepin des exemples de ce chapitre correspond au nom PYDASC_14. Nous avons
expliqué dans l’introduction comment récupérer les exemples.

Décomposition en valeurs singulières (SVD)


La pierre angulaire des traitements de réduction des dimensions correspond à une opération
d’algèbre linéaire qui s’appelle la décomposition en valeurs singulières (SVD pour Single Value
Decomposition). La SVD reçoit en entrée une matrice pour en produire trois en sortie. Ces
dernières permettent de reconstruire la maîtrise d’entrée en les multipliant entre elles. (Vous
trouverez une présentation des bases mathématiques sur la page Wikipédia correspondante.)
Voici la formule générale de la SVD :

M = U * s * Vh

En sachant que :
» U : contient toutes les informations à propos des lignes ou observations ;
» VH : contient toutes les informations concernant les colonnes ou caractéristiques ;
» S : contient la description du processus SVD (une sorte de journal).
Lorsque l’objectif est de réduire les dimensions, le fait de créer trois matrices à partir d’une
semble partir dans le mauvais sens. Effectivement, vous pourriez penser que cette technique va
générer encore plus de données. Mais la magie est au cœur de la décomposition en valeurs
singulières SVD. En construisant les nouvelles matrices, nous séparons les informations
concernant les lignes de celles concernant les colonnes (qui étaient liées dans la première
matrice). Toutes les informations utiles sont ramenées dans les premières colonnes des nouvelles
matrices.
La matrice résultante s permet de savoir comment la compression a été réalisée. La somme de
toutes les valeurs dans s indique combien d’informations étaient présentes dans la matrice de
départ. De plus, chaque valeur dans s indique la quantité de données qui a été accumulée dans
chacune des colonnes respectives de U et de Vh.
Pour mieux comprendre, il faut s’intéresser aux valeurs individuelles. Si, par exemple, la somme
de s vaut 100 et que la première valeur de s vaut 99, cela signifie que 99 % de l’information est
dorénavant ramenée dans la première colonne de U et de Vh. Autrement dit, vous pouvez sans
souci abandonner toutes les autres colonnes après la première sans perdre d’informations
importantes pour la réussite de votre projet d’exploration des données.

Pratique de la réduction de dimensions


Voyons maintenant comment le langage Python peut nous aider à réduire la complexité.
L’exemple qui suit propose une première approche que vous pouvez utiliser dans de nombreuses
autres applications.

import numpy as np
A = np.array([[1, 3, 4], [2, 3, 5], [1, 2, 3], [5, 4, 6]])
print(A)

Ce code source affiche la matrice A :

[[1 3 4]
[2 3 5]
[1 2 3]
[5 4 6]]

Cette matrice contient les données que nous voulons réduire, sous forme de quatre observations
avec trois caractéristiques chacune. Nous pouvons utiliser le module linalg de NumPy pour
appeler la fonction svd() qui va répartir cette matrice en trois variables U, s et Vh.

U, s, Vh = np.linalg.svd(A, full_matrices=False)
print(np.shape(U), np.shape(s), np.shape(Vh))
print(s)

Le résultat affiche les profils (shape) des trois matrices produites puis affiche le contenu de s :

(4, 3) (3,) (3, 3)


[12.26362747 2.11085464 0.38436189]

La matrice U qui incarne les lignes possède quatre valeurs. La matrice Vh est une matrice
carrée ; ses trois lignes correspondent aux colonnes de la matrice de départ. Enfin, la matrice s
est diagonale, c’est-à-dire que tous ses éléments sont à zéro, sauf les diagonales. La longueur de
la diagonale correspond à celle des trois colonnes de départ. Pour obtenir des pourcentages,
vous additionnez les trois valeurs, ce qui donne 14,75 puis vous divisez cette valeur par celle de
la colonne individuelle. Par exemple, 12.26 / 14.75 donne 0.83 soit environ 83 %. Nous
constatons donc que la plupart des valeurs sont bien dans le premier élément, ce qui confirme
que la première colonne contient le plus d’informations (environ 83 %). La deuxième colonne en
contient 14 % et la troisième le reste.
Vous pouvez ainsi vérifier que la décomposition SVD tient ses promesses en étudiant directement
son résultat. L’exemple suivant reconstruit la matrice de départ avec la fonction de NumPy
nommée dot(). Elle multiplie U, s (en diagonale) et Vh. Cette fonction réalise des multiplications
matricielles, et non arithmétiques. Voici une reconstruction de matrice :

print(np.dot(np.dot(U, np.diag(s)), Vh))

L’affichage prouve que nous avons reconstruit la matrice A :

[[1. 3. 4.]
[2. 3. 5.]
[1. 2. 3.]
[5. 4. 6.]]

La reconstruction est parfaite, mais cela n’a rien d’étonnant, dans la mesure où pour l’instant,
nous n’avons supprimé aucune colonne dans la matrice U. Nous n’avons fait que restructurer les
données afin de décorréler les nouvelles variables, ce qui sera également utile pour les
algorithmes de regroupement clustering que nous verrons dans le Chapitre 15.
Lorsque vous utilisez une décomposition SVD, vous vous intéressez surtout à la matrice
résultante U qui incarne les lignes, car c’est elle qui va remplacer le jeu de données de départ.

Voyons maintenant comment faire réellement un peu de réduction des données. Commençons
par exclure la troisième colonne de la matrice, celle qui a le moins de poids :

print(np.round(np.dot(np.dot(U[:,:2], np.diag(s[:2])),
Vh[:2,:]),1)) # reconstruction k=2

Voici la tentative de reconstruction de la matrice de départ, à partir des seules deux


composantes conservées :

[[1. 2.8 4.1]


[2. 3.2 4.8]
[1. 2. 3. ]
[5. 3.9 6. ]]

Les valeurs sont quasiment les bonnes. Cela confirme que vous pouvez oublier la dernière
composante et vous servir de la matrice U en remplacement de celle de départ. Les valeurs ne
divergent que d’un ou deux dixièmes. Poursuivons la réduction en supprimant maintenant
également la deuxième colonne de la matrice U :

print(np.round(np.dot(np.dot(U[:,:1], np.diag(s[:1])),
Vh[:1,:]),1)) # reconstruction k=1

Tentons de reconstruire la matrice de départ à partir d’une seule composante :

[[2.1 2.5 3.7]


[2.6 3.1 4.6]
[1.6 1.8 2.8]
[3.7 4.3 6.5]]

Nous retrouvons beaucoup moins bien les valeurs de départ. Certaines erreurs sont de presque
un point de différence. En revanche, l’essentiel des informations numériques reste intact. Vous
pouvez donc utiliser la matrice U à la place des données initiales. Imaginez le potentiel de cette
technique sur une matrice comptant des centaines de colonnes ! Vous allez pouvoir les
transformer en une matrice compacte en supprimant la majorité des colonnes.
La partie délicate consiste bien sûr à choisir combien de colonnes conserver. Vous pouvez
surveiller la quantité d’informations restant disponible et dans quelles colonnes en faisant
produire une somme cumulée de la matrice diagonale s au moyen de la fonction cumsum() de
NumPy. Une bonne règle consiste à faire en sorte de conserver de 70 à 85 % des informations de
départ (mais cette règle n’est pas figée). Tout dépend de l’importance qu’il y a à pouvoir
reconstruire le jeu de données initial.

La SVD pour mesurer l’invisible


La décomposition en valeurs singulières SVD peut comprimer les données de façon telle qu’il est
possible dans certains cas de produire de nouvelles caractéristiques intéressantes, et pas
seulement de réduire le nombre de variables. Vous pourriez par exemple avoir considéré les trois
colonnes de la matrice U comme de nouvelles caractéristiques.
Si les données d’entrée contiennent des indices ténus à propos d’un motif apparemment caché,
vous pouvez avec une décomposition SVD faire converger ces indices pour obtenir de nouvelles
perspectives. Cette découverte est particulièrement fructueuse avec des données contenant des
éléments d’information dans les domaines suivants :
» Les documents de type texte contiennent des indices d’idées et de catégories
significatives. En parcourant des pages Web et des blogs, vous vous faites une idée des
thèmes traités. La décomposition peut faire de même en déduisant une classification
intéressante à partir d’un groupe de documents ou à partir de quelques sujets en particulier
abordés dans ces documents.
» Les critiques de films ou de livres peuvent guider vers vos préférences
personnelles et ouvrir sur des catégories de produits plus vastes. Si vous aimez la
série de films Star Trek et le déclarez sur un site, il devient facile de savoir quels autres films
vous aimeriez, mais également quels produits de consommation courante, voire quel type de
personnalité vous correspond.

Une méthode qui se base sur la décomposition SVD correspond à l’indexation sémantique latente
LSI (Latent Semantic Indexation). Elle parvient assez bien à réunir des documents et des mots
en se basant sur le fait que des mots différents tendent à avoir la même signification dans des
contextes similaires. Ce genre d’analyse va plus loin que les simples synonymes, en traitant des
concepts de groupement sémantique. C’est ainsi qu’une analyse LSI appliquée à des comptes-
rendus sportifs permettra de retrouver les équipes de football d’un championnat uniquement par
détection simultanée de plusieurs noms d’équipes dans les articles, sans avoir une connaissance
préalable de ce qu’est une équipe de football ou un championnat.

Analyse de facteurs et PCA


La décomposition DVS travaille directement sur les valeurs numériques, mais vous pouvez
également exprimer les données sous forme de relations entre les variables. Chaque
caractéristique possède une plage de variation et vous pouvez calculer la variance autour de la
moyenne. Plus la variance est élevée, plus la variable contient d’informations. En outre, si vous
intégrez une variable à un groupe, vous pouvez ensuite comparer les variances de deux variables
pour déterminer si elles sont corrélées, c’est-à-dire à quel point elles prennent des valeurs
similaires.
En étudiant toutes les corrélations possibles d’une variable avec les autres du même groupe,
vous finissez par aboutir à deux types de variance :
» Variance unique : certaines variances restent uniques à la variable examinée, qui ne peut
pas être associée à l’évolution d’aucune autre variable.
» Variance partagée ou commune : la variance est partagée au moins en partie avec celle
d’autres variables, ce qui confirme une redondance dans les données. Cette redondance
signifie que vous pourrez préserver la même information en partant de valeurs légèrement
différentes dans d’autres caractéristiques, et pour de nombreuses autres observations.

Il s’agit ensuite bien sûr de chercher les causes de cette variance partagée. Pour répondre à
cette question, et pour exploiter les variances uniques et partagées, sont apparues deux
nouvelles techniques d’analyse : l’analyse de facteurs et l’analyse par composantes principales
PCA (Principal Components Analysis, aussi connue sous le sigle français ACP).

Le modèle psychométrique
Bien avant l’invention des premiers algorithmes d’apprentissage machine, la discipline qui
s’intéresse aux mesures psychologiques, la psychométrique, a tenté de chercher des solutions
statistiques aux problèmes de grandes dimensions dans l’étude des personnalités. Comme
d’autres aspects de nos êtres, notre personnalité ne peut pas être mesurée directement. Il
n’existe ainsi pas de mesure quantitative du degré d’intelligence ou d’introversion d’une
personne. Les tests psychologiques et les questionnaires ne fournissent que des indices de
tendance.
Les chercheurs en psychologie connaissaient l’outil de décomposition en valeurs singulières et
ont tenté de l’utiliser. Ils ont alors été attirés par les variances partagées : lorsque deux variables
varient de concert, on peut en conclure que la cause des variations est la même. Les
psychologues ont ainsi créé l’analyse de facteurs. Au lieu d’appliquer la décomposition SVD aux
données d’entrée, ils vont l’appliquer à une matrice contenant les variances partagées, espérant
ainsi compresser toutes les informations et produire de nouvelles caractéristiques qu’ils ont
décidé d’appeler facteurs.

Recherche des facteurs cachés


Partons du jeu de données de test Iris pour pratiquer l’analyse de facteurs :

from sklearn.datasets import load_iris


from sklearn.decomposition import FactorAnalysis
iris = load_iris()
X = iris.data
Y = iris.target
cols = [s[:12].strip() for s in iris.feature_names]
factor = FactorAnalysis(n_components=4).fit(X)

Nous chargeons les données puis stockons toutes les caractéristiques prédictives. Nous
initialisons ensuite la classe FactorAnalysis en la paramétrant pour qu’elle recherche quatre
facteurs, puis ajustons les données. Les résultats sont visibles par l’attribut components_ qui
fournit un tableau contenant les mesures des relations entre les facteurs venant d’être créés
dans les lignes, avec les caractéristiques de départ dans les colonnes :

import pandas as pd
print(pd.DataFrame(factor.components_, columns=cols))

L’affichage des résultats d’analyse montre les relations entre les lignes de facteurs et les
variables initiales en colonnes. Les valeurs peuvent être interprétées comme des corrélations :

sepal length sepal width petal length petal width


0 0.707227 -0.153147 1.653151 0.701569
1 0.114676 0.159763 -0.045604 -0.014052
2 -0.000000 0.000000 0.000000 0.000000
3 -0.000000 0.000000 0.000000 -0.000000

Une valeur positive à l’intersection entre un facteur et une caractéristique signe une relation de
proportion positive entre les deux éléments. Une valeur négative signifie que les éléments
divergent et évoluent en sens contraire. Dans l’exemple avec le jeu d’iris, il ne faut conserver
que deux facteurs et non quatre, car deux seulement sont reliés de façon significative aux
caractéristiques. Vous pouvez vous servir de ces deux facteurs comme nouvelles variables parce
qu’elles rendent accessibles une caractéristique importante qui était cachée, alors que les
données d’entrée ne fournissaient que des indices.
Vous devez tester plusieurs valeurs pour n_components parce que vous ne savez pas au départ
combien de facteurs sont cachés dans les données. Vous saurez que vous demandez trop de
facteurs pour l’algorithme lorsqu’il va générer des facteurs avec des valeurs très faibles ou
nulles dans le tableau components_.

Utilisation de composantes au lieu de facteurs


S’il est ainsi possible d’appliquer une décomposition SVD à une variance partagée, vous aurez
peut-être envie de vous en servir aussi pour les autres variances. En partant d’une matrice
initiale légèrement modifiée, il devient possible de réduire et de compresser toutes les relations
dans les données, d’une façon proche de celle dont travaille la SVD. Le résultat est assez proche
de celui de la SVD : il correspond à l’analyse en composantes principales PCA (Principal
Components Analysis, parfois abrégé en ACP). Dans ce cas, les nouvelles caractéristiques sont
des composantes. Elles se distinguent des facteurs par le fait qu’elles ne sont pas la cause à
l’origine de la structure des données, mais ne sont que des données restructurées. Vous pouvez
les considérer comme des sommes intelligentes et volumineuses des variables sélectionnées.
Les deux analyses SVD et PCA sont très proches en datalogie, mais l’analyse par composantes
n’est pas dépendante de l’échelle des caractéristiques de départ parce qu’elle travaille sur des
mesures de corrélation qui sont toutes limitées aux valeurs entre -1 et +1. L’analyse PCA cherche
à reconstruire les relations entre les variables, et c’est pourquoi elle donne d’autres résultats
que l’analyse SVD.
N. d. T. : Vous aurez constaté que nous adoptons en général les acronymes et sigles anglais pour
les techniques présentées.

Obtention d’une réduction de dimension


La réalisation d’une analyse par composantes PCA ressemble à l’analyse de facteurs. Elle s’en
distingue par le fait que vous ne spécifiez pas le nombre de composantes à extraire. Vous
déciderez cela plus tard après avoir étudié l’attribut explained_variance_ratio_ qui fournit en
pourcentage la quantité d’informations que porte chaque composant extrait. L’exemple suivant
réalise ce traitement :

from sklearn.decomposition import PCA


import pandas as pd
pca = PCA().fit(X)
print(‘Variance expliquée par composante: %s’
% pca.explained_variance_ratio_)
print(pd.DataFrame(pca.components_, columns=cols))

Le résultat permet de voir comment la variance initiale du jeu est distribuée parmi les
composantes : la première composante compte par exemple pour 92,5 % de la variance présente
au départ. La matrice de composantes produite montre chacune des composantes dans les lignes
en relation avec chacune des variables de départ dans les colonnes :

Variance expliquée par composante: [0.92461621 0.05301557 0.01718514


0.00518309]
sepal length sepal width petal length petal width
0 0.361590 -0.082269 0.856572 0.358844
1 0.656540 0.729712 -0.175767 -0.074706
2 -0.580997 0.596418 0.072524 0.549061
3 0.317255 -0.324094 -0.479719 0.751121

Le tableau de vecteurs trouvé dans explained_variance_ratio_ montre que l’essentiel des


informations a été concentré dans la première composante, avec 92,5 %. Nous avons vu le même
genre de résultat lors de l’analyse de facteurs. Il est donc possible de réduire tout le jeu de
données à deux composantes, réduisant ainsi la quantité de bruit et d’informations redondantes
par rapport au jeu de données initial.

Extraction d’informations avec t-SNE


Puisque les deux techniques SVD et PCA permettent de réduire la complexité des données, nous
pouvons nous servir de leur travail pour la visualisation. Cependant, les nuages de points
obtenus à partir d’une analyse PCA ne suffisent pas à la visualisation parce qu’il faut disposer
d’un plus grand nombre de points pour comprendre les relations entre eux. Constatant cela, des
experts ont créé des algorithmes permettant des réductions de dimensions non linéaires, et
notamment l’algorithme t-SNE (Stochastic Neighbor Embedding). Il devient ainsi possible
d’observer les relations dans des jeux de données comprenant des centaines de variables, en
utilisant des nuages de points simples en deux dimensions.
L’algorithme t-SNE commence par une projection aléatoire des données dans le nombre de
dimensions demandé sous forme de points (en général pour deux dimensions). L’algorithme entre
ensuite dans une boucle de répétition dans laquelle il tente de déplacer les points qui
correspondent à des exemples similaires dans le jeu pour les rapprocher les uns des autres (la
similarité est calculée par probabilité). Inversement, il éloigne les points qui sont trop différents
les uns des autres. Au bout de quelques tours, les points similaires sont regroupés, et les autres
écartés. Cette réorganisation améliore la lisibilité des points de données et vous permet de les
étudier pour produire des conclusions quant à la signification de ces données.
Notre exemple utilise la série de chiffres en écriture manuscrite livrée dans le paquetage Scikit-
learn. Il s’agit d’images en nuances de gris des chiffres de 0 à 9 sous forme de matrices de huit
sur huit valeurs entre zéro et un. Ce sont des niveaux, le 0 correspondant au noir et le 1 au
blanc.

from sklearn.datasets import load_digits


digits = load_digits()
X = digits.data
ground_truth = digits.target

from sklearn.manifold import TSNE


tsne = TSNE(n_components=2,
init=’pca’,
random_state=0,
perplexity=50,
early_exaggeration=25,
n_iter=300)
Tx = tsne.fit_transform(X)

Après chargement du jeu de données, nous pouvons lancer l’algorithme t-SNE pour extraire les
données :

from sklearn.manifold import TSNE


tsne = TSNE(n_components=2,
init=’pca’,
random_state=0,
perplexity=50,
early_exaggeration=25,
n_iter=300)
Tx = tsne.fit_transform(X)

Vous paramétrez trois éléments qui contribuent à la qualité du résultat : perplexity,


early_exaggeration et n_iter. Vous pouvez modifier légèrement les valeurs pour voir l’impact
sur le résultat. Une fois le jeu de données ainsi réduit, nous pouvons le visualiser et ajouter les
labels des chiffres dans la région du graphe où se concentrent les exemples similaires, ainsi :

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.xticks([], [])
plt.yticks([], [])
for target in np.unique(ground_truth):
selection = ground_truth==target
X1, X2 = Tx[selection, 0], Tx[selection, 1]
c1, c2 = np.median(X1), np.median(X2)
plt.plot(X1, X2, ‘o’, ms=5)
plt.text(c1, c2, target, fontsize=18)

La Figure 14.1 montre le résultat. Nous pouvons constater que certains chiffres, notamment le 0,
le 6 et le 4, se distinguent aisément des autres, alors que le 3 et le 9, voire le 5 et le 8 risquent
plus d’être confondus.

Figure 14.1 : Projection produite par l’algorithme t-SNE sur des chiffres manuscrits.

Quelques applications de décomposition


Il n’est pas simple de comprendre le fonctionnement des algorithmes de la famille SVD, c’est-à-
dire les techniques de décomposition des données, en raison de la complexité au niveau
mathématique et de nombreuses variantes telles que la factorisation, la PCA et la SVD elle-
même. Vous saurez mieux quand et comment adopter ces puissants outils en parcourant
quelques exemples complets. La fin de ce chapitre est donc dédiée à la présentation pratique des
algorithmes dans trois domaines :
» une recherche d’images de visages d’un moteur de recherche ou publiées dans un réseau
social ;
» une catégorisation automatique des articles d’un blog ou des questions sur une page de
forum ;
» la production de conseils d’achat sur des sites d’e-commerce.

Reconnaissance de visages par analyse PCA


L’exemple qui suit montre comment exploiter des images de visages, afin de comprendre
comment les sites de réseaux sociaux les catégorisent avec des labels ou des noms.

from sklearn.datasets import fetch_olivetti_faces


dataset = fetch_olivetti_faces(shuffle=True,
random_state=101)
train_faces = dataset.data[:350,:]
test_faces = dataset.data[350:,:]
train_answers = dataset.target[:350]
test_answers = dataset.target[350:]

Nous commençons par importer un jeu de données de visages nommé Olivetti, disponible dans
Scikit-learn. Pour l’exemple, nous divisons le jeu d’images identifiées en un jeu d’entraînement et
un jeu de test. Vous allez supposer que vous connaissez les noms dans le jeu d’entraînement,
mais pas ceux du jeu qui servira à tester. L’objectif est donc d’associer des images du jeu de test
aux images les plus ressemblantes du jeu d’entraînement.
Le jeu Olivetti contient 400 photos prises de 40 personnes, donc 10 photos par personne. Les
photos qui représentent la même personne sont prises à des moments différents, avec un
éclairage différent et des modifications dans l’expression du visage ou la présence d’accessoires
tels que des lunettes. Chaque image mesure 64 sur 64 pixels. La sérialisation de ces pixels pour
obtenir des caractéristiques aboutit à un jeu comportant 400 cas et 4 096 variables. Pour en
savoir plus au sujet de ce jeu de données, vous pouvez utiliser la commande
print(dataset.DESCR) (nous le faisons dans l’exemple). Pour d’autres détails au sujet du jeu,
voyez le site Web de AT&T Laboratories Cambridge
(https://www.cl.cam.ac.uk/research/dtg/attarchive/facedatabase.html). Commençons
par transformer puis réduire les images en appliquant l’algorithme PCA de Scikit-learn :

from sklearn.decomposition import RandomizedPCA


n_components = 25
Rpca = PCA(svd_solver=’randomized’,
n_components=n_components,
whiten=True)
Rpca.fit(train_faces)
print(‘Variance expliquée par %i composantes: %0.3f’
% (n_components, np.sum(Rpca.explained_variance_ratio_)))
compressed_train_faces = Rpca.transform(train_faces)
compressed_test_faces = Rpca.transform(test_faces)

Le résultat montre la proportion de variance correspondant aux 25 premières composantes


issues de l’analyse :

Variance expliquée par 25 composantes : 0.794

Nous avons adopté la classe RandomizedPCA, une version approximative de l’analyse PCA qui
fonctionne mieux lorsque le jeu comporte beaucoup de lignes et de variables. La décomposition
produit 25 nouvelles variables (paramètre n_com-ponents). Le paramètre whiten_True
provoque un blanchiment pour supprimer un peu de bruit de fond causé par la granularité du
texte et des photos. Nous obtenons bien 25 composantes, soit environ 80 % des informations
trouvées dans 4 096 caractéristiques.

import matplotlib.pyplot as plt


%matplotlib inline

photo = 17
print(‘La personne représentée est le sujet %i’
% test_answers[photo])
plt.subplot(1, 2, 1)
plt.axis(‘off’)
plt.title(‘Photo ‘+str(photo)+’ inconnue dans le jeu de test’)
plt.imshow(test_faces[photo].reshape(64,64),
cmap=plt.cm.gray, interpolation=’nearest’)
plt.show()

La Figure 14.2 montre que la recherche du sujet 34 fait proposer la photo 17 dans le jeu de test.
Figure 14.2 : Application cherchant des photos similaires.

En sortie de décomposition du jeu de test, l’exemple ne conserve que les données concernant la
photo 17, puis soustrait ces données de la décomposition du jeu d’entraînement. En
conséquence, le jeu d’entraînement contient les différences par rapport à la photo d’exemple.
Nous élevons les valeurs au carré pour supprimer les valeurs négatives puis nous les
additionnons par ligne, ce qui produit une série d’erreurs cumulées. Les photos les plus
ressemblantes sont celles pour lesquelles les erreurs de moindres carrés, donc celles dont les
différences sont les plus faibles.

mask = compressed_test_faces[photo,]
squared_errors = np.sum((compressed_train_faces - mask)**2,
axis=1)
minimum_error_face = np.argmin(squared_errors)
most_resembling = list(np.where(squared_errors < 20)[0])
print(«Sujet le plus ressemblant dans le jeu d’entraînement: %i»
% train_answers[minimum_error_face])

L’extrait précédent indique le numéro de l’image de la personne identifiée qui ressemble le plus,
ce qui est bien en accord avec le sujet choisi dans le jeu de test :

Sujet le plus ressemblant dans le jeu d’entraînement: 34

Nous pouvons vérifier le traitement réalisé en affichant la photo 17 du jeu de test avec les trois
images les mieux classées du jeu d’entraînement, celles qui y ressemblent le plus (Figure 14.3) :

import matplotlib.pyplot as plt


plt.subplot(2, 2, 1)
plt.axis(‘off’)
plt.title(‘Visage ‘+str(photo)+’ inconnu à tester’)
plt.imshow(test_faces[photo].reshape(64, 64),
cmap=plt.cm.gray,
interpolation=’nearest’)
for k,m in enumerate(most_resembling[:3]):
plt.subplot(2, 2, 2+k)
plt.title(‘Visage connu similaire ‘+str(m))
plt.axis(‘off’)
plt.imshow(train_faces[m].reshape(64, 64),
cmap=plt.cm.gray,
interpolation=’nearest’)
plt.show()
Figure 14.3 : Affichage des visages connus les plus ressemblants à l’image de test.

L’image la plus ressemblante n’est qu’une version avec une résolution différente de celle à
identifier, mais les deux autres montrent une pose différente de la même personne que la
photo 17. Nous constatons donc que l’analyse PCA peut se fonder sur une image échantillon pour
trouver d’autres photos de la même personne.

Extraction de sujets par factorisation NMF


Un des autres domaines d’application des algorithmes de réduction de données correspond au
traitement des données de type texte. Il a été en effet constaté que lorsque plusieurs personnes
échangeaient sur un forum, elles avaient tendance à n’utiliser qu’un vocabulaire propre au sujet
de discussion. Les participants s’appuient sur un ensemble de significations partagées. De ce
fait, lorsque vous devez travailler sur un ensemble de textes sans savoir de quoi ces textes
ressortent, vous pouvez inverser le raisonnement : vous cherchez des groupes de mots qui ont
tendance à se retrouver souvent ensemble. Une réduction de dimensions produit alors un groupe
qui fournit des indices du sujet concerné.
L’analyse à réaliser convient parfaitement à la famille SVD de décomposition de valeurs
singulières. En réduisant le nombre de colonnes, les caractéristiques (les mots dans un
document) vont se regrouper dans des dimensions ; il ne restera plus qu’à chercher les mots
ayant les plus forts scores pour trouver les sujets. Les deux techniques SVD et PCA permettent
de trouver des relations positives ou négatives dans les nouvelles dimensions. Le sujet peut donc
être déterminé en constatant la présence d’un mot (valeur positive élevée) ou l’absence de ce
mot (valeur négative forte). Cette double pondération peut paraître contre-intuitive pour des
humains. Fort heureusement, Scikit-learn propose également la classe de factorisation de
matrices nommée NMF (Non-negative Matrix Factorization) qui permet de n’associer que
positivement une caractéristique initiale aux dimensions résultantes.
Notre exemple commence par charger le jeu de données 20Newsgroups déjà rencontré puis ne
sélectionne que les articles concernant des objets à vendre (for-sale). Nous supprimons ensuite
les en-têtes, pieds et citations.

from sklearn.datasets import fetch_20newsgroups


dataset = fetch_20newsgroups(shuffle=True,
categories = [‘misc.forsale’],
remove=(‘headers’, ‘footers’, ‘quotes’),
random_state=101)
print(‘Posts: %i’ % len(dataset.data))

L’exemple indique le nombre d’articles trouvés :

Posts: 585

Nous pouvons alors importer la classe TfidfVectorizer et la paramétrer pour qu’elle élimine
les mots vides et ne garder que les mots significatifs. Nous obtenons une matrice dont toutes les
colonnes pointent vers des mots différents.

from sklearn.feature_extraction.text import TfidfVectorizer


from sklearn.decomposition import NMF

vectorizer = TfidfVectorizer(max_df=0.95, min_df=2,


stop_words=’english’)
tfidf = vectorizer.fit_transform(dataset.data)
n_topics = 5
nmf = NMF(n_components=n_topics,
random_state=101).fit(tfidf)

Nous avons déjà rencontré la technique TF-IDF dans le Chapitre 8. Rappelons qu’elle s’intéresse
à la fréquence des mots dans un document. Elle établit un poids selon la rareté de chaque mot,
ce qui permet d’écarter les mots qui ne sont d’aucun intérêt pour classer ou identifier les
documents. Cela permet notamment d’éliminer les mots du langage commun rencontrés dans les
discussions.
Comme avec d’autres algorithmes du module sklearn.decomposition, le paramètre
n_components sert à choisir le nombre de composantes désirées. Vous augmentez cette valeur
pour rechercher plus de sujets. Plus cette valeur est élevée, plus va diminuer le taux d’erreur
renvoyé par la méthode reconstruction_err_. Vous déciderez quand arrêter cet ajustement en
arbitrant entre le temps de traitement supplémentaire et le supplément de sujets obtenu.
La dernière partie du script informe sur les cinq sujets trouvés. La lecture des mots vous permet
de choisir quelle signification donner à ces sujets, en tenant compte des caractéristiques
correspondantes. Par exemple, les mots anglais drive, hard, card et floppy ressortent tous au
domaine de l’informatique. Bien sûr, le mot peut également n’avoir aucune ambiguïté, par
exemple, comics, car, stereo ou games.

feature_names = vectorizer.get_feature_names()
n_top_words = 15
for topic_idx, topic in enumerate(nmf.components_):
print(‘Topic #%d:’ % (topic_idx+1),)
topics = topic.argsort()[:-n_top_words - 1:-1]
print(‘ ‘.join([feature_names[i] for i in topics]))

Les sujets sont présentés dans l’ordre avec leurs mots-clés les plus représentatifs. Pour explorer
le modèle qui en résulte, vous pouvez étudier l’attribut nommé components_ du modèle NMF
entraîné. Il s’agit d’un tableau numérique de type ndarray qui contient des valeurs positives
pour tous les mots associés au sujet. Vous pouvez récupérer les index des associations les plus
fortes au moyen de la méthode argsort() ; les valeurs les plus élevées correspondent aux mots
les plus représentatifs. L’instruction suivante extrait les index des mots les plus représentatifs
pour le sujet 1 :

print(nmf.components_[0,:].argsort()[:-n_top_words-1:-1])

Vous obtenez une liste d’index, chacun correspondant à un des mots :

[1075 1459 632 2463 740 888 2476 2415 2987 10 2305 1 3349 923 2680]

Vous utilisez ces accès index pour obtenir des chaînes de texte en les appelant depuis un tableau
produit par la méthode get_feature_names() que vous appliquez à l’objet TfidfVectorizer
qui a été préparé auparavant. L’extrait suivant montre comment extraire le mot qui correspond à
l’index du mot le plus significatif pour ce sujet :

word_index = 1075
print(vectorizer.get_feature_names()[word_index])

Nous apprenons ainsi que ce mot est « condition » , et concerne donc l’état d’un objet mis en
vente :

condition

Suggestions cinématographiques
Un autre domaine applicatif de réduction des données correspond au système qui génère des
recommandations d’achat ou d’activités qui pourraient vous intéresser. Vous avez certainement
vu les résultats de ces applications sur les sites de commerce électronique, à partir du moment
où vous vous êtes identifié et avez visité au moins quelques pages. Vous montrez votre intérêt
pour des produits en naviguant de page en page, et encore mieux en passant à la caisse. Votre
comportement et celui d’autres visiteurs permettent de vous proposer de nouvelles opportunités,
ce qui correspond au mécanisme de filtrage collaboratif.
Avec la décomposition SVD, vous pouvez réaliser ce système de suggestion collaborative, en vous
basant par exemple sur des fréquences calculées à partir des achats d’autres clients, ou à partir
de notations. Vous pouvez ainsi générer des suggestions pertinentes, même pour des produits
qui se vendent peu ou sont nouveaux. En guise d’exemple, nous allons exploiter une base de
données très connue produite par le site Web MovieLens à partir des notes attribuées par des
cinéphiles à propos de films. Puisqu’il s’agit de données externes, vous devez d’abord
télécharger le fichier compressé ml-m1.zip à l’adresse suivante :

http://files.grouplens.org/datasets/movielens/ml-1m.zip

Une fois que le fichier compressé a été récupéré, vous devez le déplacer dans votre répertoire de
travail PYDASC puis le décompresser pour obtenir un sous-répertoire ml-1m. Voici comment
vérifier que vous êtes bien dans le répertoire de travail :

import os
print(os.getcwd())
os.chdir(«.») # Si vous avez besoin d’en changer
print(os.getcwd())

N. d. T. : Si vous avez besoin de changer de sous-répertoire, utilisez la commande os.chdir()


en indiquant le nouveau chemin entre guillemets.

Nous pouvons alors exécuter le code suivant :

import pandas as pd
from scipy.sparse import csr_matrix
users = pd.read_table(‘ml-1m/users.dat’, sep=’: :’,
header=None, names=[‘user_id’, ‘gender’,
‘age’, ‘occupation’, ‘zip’], engine=’python’)
ratings = pd.read_table(‘ml-1m/ratings.dat’, sep=’: :’,
header=None, names=[‘user_id’, ‘movie_id’,
‘rating’, ‘timestamp’], engine=’python’)
movies = pd.read_table(‘ml-1m/movies.dat’, sep=’: :’,
header=None, names=[‘movie_id’, ‘title’,
‘genres’], engine=’python’)
MovieLens = pd.merge(pd.merge(ratings, users), movies)

Nous utilisons la librairie pandas pour charger les différentes tables, puis les fusionner en nous
basant sur les caractéristiques portant le même nom (les deux variables user_id et movie_id) :

ratings_mtx_df = MovieLens.pivot_table(values=’rating’,
index=’user_id’, columns=’title’, fill_value=0)
movie_index = ratings_mtx_df.columns

Toujours grâce à pandas, nous créons une table croisée qui associe les utilisateurs dans les
lignes aux titres de films dans les colonnes. Un index des films permettra de savoir à quel film
correspond chaque colonne :

from sklearn.decomposition import TruncatedSVD


recom = TruncatedSVD(n_components=15, random_state=101)
R = recom.fit_transform(ratings_mtx_df.values.T)

Grâce à la classe TruncatedSVD, nous réduisons la table de données pour qu’elle n’ait plus
que 10 composantes. Cette classe propose un algorithme plus souple que celui de Scipy nommé
linalg.svd qui nous a servi dans les exemples précédents. TruncatedSVD produit une matrice
résultante ayant exactement la forme que vous désirez grâce au paramètre n_components, ce qui
produit un résultat plus rapidement en consommant moins de mémoire (les matrices résultantes
complètes ne sont pas calculées).
En calculant la matrice Vh, vous réduisez le nombre de notations des utilisateurs similaires (les
scores sont exprimés par lignes) afin d’aboutir à des dimensions compressées qui expriment des
goûts et des préférences généraux. Prenez bien note du fait que puisque vous cherchez la
matrice Vh (la réduction en colonnes des titres de films), alors que l’algorithme ne fournit que la
matrice U (la décomposition en lignes), il est nécessaire d’injecter la transposition de la table de
données (cette transposition intervertit lignes et colonnes, et vous obtenez la sortie de
TruncatedSVD qui est la matrice Vh). Vous pouvez ensuite interroger pour un film en particulier :

movie = ‘Star Wars: Episode V - The Empire Strikes Back (1980)’


movie_idx = list(movie_index).index(movie)
print(“movie index: %i” %movie_idx)
print(R[movie_idx])

Cette opération fournit l’index d’un épisode de Star Wars avec ses coordonnées SVD :

movie index: 3154


[184.72254552 -17.77612872 47.33450866 51.4664494 47.92058216
17.65033116 14.3574635 -12.82219207 17.51347857 5.46888807
7.5430805 -0.57117869 -30.74032355 2.4088565 -22.50368497]

Le titre du film permet de trouver la colonne correspondante, qui est celle de l’index 3154 ici.
Nous pouvons alors afficher les valeurs de dix composantes, ce qui donne le profil du film. Nous
pouvons ensuite chercher tous les films dont les scores ressemblent à ceux du film étudié (tous
ceux ayant une forte corrélation avec lui). Pour y parvenir, une bonne technique consiste à
calculer la matrice de corrélation de tous les films, d’obtenir la tranche qui correspond à celui
qui vous intéresse, puis à chercher dans ses données tous les titres de films les plus apparentés
par indexation (montrant une corrélation positive forte, d’au moins 0.98 par exemple). C’est ce
que fait le code source suivant :

import numpy as np
correlation_matrix = np.corrcoef(R)
P = correlation_matrix[movie_idx]
print(list(movie_index[(P > 0.95) & (P < 1.0)]))

Nous obtenons ainsi les noms des films qui ressemblent à celui proposé, ce qui constitue bien un
générateur de suggestions basées sur des préférences cinématographiques :

[‘Raiders of the Lost Ark (1981)’, ‘Star Wars: Episode IV - A New Hope
(1977)’, ‘Star Wars: Episode VI - Return of the Jedi (1983)’, ‘Terminator,
The (1984)’]

Les amateurs de Star Wars apprécieraient plusieurs autres films, et notamment les
épisodes 4 et 6 de la saga. Ils aimeraient sans doute également Les Aventuriers de l’arche
perdue (Raiders of the Lost Ark) puisque l’acteur principal est le même dans tous ces films
(Harrison Ford).
La décomposition SVD trouve nécessairement la meilleure approche pour associer une ligne ou
une colonne de données et pour trouver des interactions complexes ou des relations que vous ne
soupçonnez même pas. Vous n’avez pas besoin d’émettre des hypothèses de départ : c’est une
approche entièrement pilotée par les données.
N. d. T. : Dans les deux précédents exemples, nous n’avons pas traduit les données puisqu’elles
proviennent de jeux de données disponibles uniquement en anglais.
Chapitre 15
Regroupements (clustering)
DANS CE CHAPITRE :
» Exploration des possibilités du regroupement non supervisé

» Les k-moyennes avec petits et grands volumes de données

» L’alternative DBScan

’une des compétences que les êtres humains pratiquent depuis l’aube de notre histoire
L consiste à classer les choses du monde en catégories, à partir de caractéristiques que
partagent différents objets du réel, selon des critères qui dépendent de celui qui réalise ce
classement. Les hommes des cavernes ont classé le monde de la nature en distinguant d’abord
les plantes des animaux, puis entre ceux qui leur étaient utiles et ceux qui pouvaient leur
nuire. De nos jours, les départements de marketing classent les clients et prospects en
différents segments cibles puis les visent en les attirant dans des plans marketing sophistiqués.
La classification est un processus indispensable pour construire de nouvelles connaissances, en
permettant de regrouper les objets similaires, par exemple :
» désigner tous les éléments d’une classe par un seul nom ;
» produire une synthèse des caractéristiques remarquables au moyen d’un type de classe
servant d’exemple ;
» associer des actions spécifiques à une classe ou mémoriser des connaissances
particulières, de façon automatique.

L’exploitation des énormes flux de données actuels réclame cette même compétence de
classement, mais à une tout autre échelle. Pour pouvoir détecter des groupes de signaux
inconnus masqués dans les données, il faut utiliser des algorithmes capables d’apprendre
comment associer des exemples à certaines classes (apprentissage supervisé) mais également
capables de découvrir de nouvelles classes intéressantes qui étaient passées sous silence
(apprentissage non supervisé).
Vos activités de datalogue vont surtout consister à mettre en pratique vos compétences de
prédiction, mais vous devrez également pouvoir déceler de nouvelles informations présentes
dans les données. Vous devrez par exemple souvent repérer de nouvelles caractéristiques pour
renforcer le pouvoir de prédiction de vos modèles, trouver des solutions praticables pour
comparer les données de façon complexe et repérer des points communs dans les flux des
réseaux sociaux.
L’approche de classification pilotée par les données correspond au regroupement ou clustering.
Elle vous sera d’un grand secours dans vos projets dès que vous aurez besoin d’obtenir de
nouvelles perspectives alors qu’il vous manque des labels (étiquettes) de données au départ, ou
lorsque vous aurez besoin d’en créer de nouvelles.
Les techniques de regroupement englobent tout un ensemble de méthodes de classification
non supervisée. Elles permettent de créer des classes pertinentes par traitement direct des
données sans connaissances préalables, ni hypothèses au sujet des groupes qui peuvent être
présents. Les algorithmes supervisés ont besoin d’exemples avec des labels de classes ; ceux
qui sont non supervisés trouvent en toute autonomie quels seraient les labels les plus
appropriés.
Le regroupement permet de synthétiser d’énormes volumes de données. C’est une excellente
technique pour fournir des données à un auditoire non technique, et pour alimenter un
algorithme supervisé en variables de groupes, ce qui revient à lui transmettre des informations
concentrées et significatives.
Il existe plusieurs techniques de regroupement que nous distinguons en adoptant les règles
suivantes :
» affectation de chaque exemple à un groupe unique (partitionnement) ou à plusieurs
groupes (regroupement flou, fuzzy) ;
» choix de la règle heuristique adoptée par la technique pour décider si un exemple fait ou
non partie d’un groupe ;
» spécification de la façon dont les techniques quantifient les différences entre les
observations, ce qui revient à mesurer des distances.

Vous allez la plupart du temps utiliser les techniques de partitionnement. Chaque point de
données ne peut faire partie que d’un groupe et les groupes ne se chevauchent pas ; il n’y a
pas d’appartenance multiple. Parmi les méthodes de partitionnement, vous utiliserez la plupart
du temps celle de la k-moyenne. Nous verrons dans ce chapitre d’autres méthodes qui se
basent sur des méthodes d’agglomération et de densité de données.
Les méthodes d’agglomération regroupent les données en fonction d’une distance. L’approche
de densité de données exploite l’idée que les groupes sont très denses et continus. Lorsque
vous détectez une diminution de densité en explorant une partie d’un groupe ou une série de
points, cela peut indiquer que vous êtes arrivé à une frontière de groupe.
Puisque vous ne savez pas a priori ce que vous cherchez, vous utiliserez plusieurs méthodes
pour obtenir plusieurs points de vue sur les données. La réussite d’un regroupement tient à
l’utilisation du maximum de recettes différentes, en comparant les résultats puis en essayant
de trouver une raison pour regrouper certaines observations et pas d’autres.
Le fichier calepin du code source des exemples de ce chapitre correspond à PYDASC_15.ipynb.

Regroupement par k-moyennes


L’algorithme fonctionnant par itération nommé k-moyennes est très utilisé en apprentissage
machine en raison de sa simplicité, de ses bonnes performances et de sa bonne capacité à
monter en charge pour traiter un grand nombre de points de données. Cet algorithme part du
principe qu’il existe un certain nombre de groupes dans les données, groupes appelés
également clusters. Tous les points de données d’un groupe sont distribués autour d’un point
central avec lequel ils partagent des attributs fondamentaux.
Le point central d’un groupe que l’on nomme également centroïde ou centre de masse peut
être vu comme un soleil, les points de données correspondant à des planètes qui gravitent
autour. Chaque groupe est ainsi un système séparé des autres systèmes planétaires par un très
grand espace vide. Chaque groupe est considéré comme suffisamment écarté de tous les
autres groupes. Tous les points d’un groupe sont donc d’une certaine façon apparentés tout en
étant différents de ceux des autres groupes.
L’algorithme de k-moyennes va chercher à trouver des groupes dans vos données, même s’il n’y
a en aucun à trouver. Il est donc essentiel d’étudier les groupes qui ont été trouvés afin de
vérifier si ce sont vraiment des pépites ou plutôt des illusions.

Connaissant ce mécanisme (N. d. T. : une sorte d’augmentation du contraste sémantique), vous


n’avez qu’une chose à faire : choisir le nombre de groupes que vous espérez obtenir, en faisant
une estimation ou en essayant plusieurs solutions possibles ; l’algorithme va se lancer à leur
recherche en appliquant une technique heuristique visant à détecter la position des points
centraux ou centroïdes.
Les centroïdes des groupes vont rapidement apparaître de par leurs caractéristiques
spécifiques et leur position par rapport aux autres points de données. Même si vous
commencez à choisir ces points par tâtonnement, après quelques corrections, vous finirez
toujours par les trouver, car ce sont eux qui sont entourés par le plus de points de données
apparentés et gravitant autour de ces centroïdes.

Algorithmes basés centroïdes


Voici la procédure générale de recherche d’un centroïde :
1. Choix initial d’un nombre K de groupes ou clusters.
Les centroïdes K sont sélectionnés au hasard parmi les points de données ou bien choisis
afin d’être très éloignés les uns des autres. Tous les autres points sont associés au centroïde
le plus proche de chacun, en évaluant des distances euclidiennes.
2. Formation des groupes initiaux.
3. Répétition de l’opération de regroupement jusqu’à constater que la solution n’a
plus d’effet modificateur.
Vous recalculez ensuite les centroïdes en tant que moyenne de tous les points de leur
groupe. Tous les points de données sont alors réassociés au groupe en fonction de la
distance par rapport au nouveau centroïde.
Le processus répété consistant à associer des cas au centroïde le plus probable puis à faire la
moyenne de ceux qui ont été associés en vue de trouver un nouveau centroïde va finir
graduellement par déplacer la position de chaque centroïde vers les zones dans lesquelles se
trouve le plus grand nombre de points de données associés. Vous finissez nécessairement par
trouver la position effective des centroïdes.
Cette approche ne souffre que de deux points faibles à ne pas négliger. Tout d’abord, au
départ, vous avez choisi les centroïdes au hasard, et auriez pu avoir la malchance de démarrer
aux pires endroits. Le processus itératif va alors s’arrêter sur une solution improbable, par
exemple avec un centroïde au beau milieu de deux groupes différents. Pour que votre solution
soit solide, il faut relancer l’algorithme plusieurs fois en suivant les résultats qui apparaissent ;
plus vous lancerez d’essais, plus vous vous approcherez de la bonne solution. L’implémentation
par Scikit-learn des k-moyennes se charge de ce travail ; il vous suffit de décider du nombre
d’exécutions à tenter. (Plus vous faites d’itérations, meilleurs sont les résultats, mais plus cela
dure longtemps.)
La seconde faiblesse concerne la distance mesurée qui est une distance euclidienne, c’est-à-
dire celle qui sépare deux points dans un plan (concept vu en sixième). Dans une application à
k-moyennes, chaque point de données correspond à un vecteur de caractéristiques. Lorsque
vous comparez la distance entre deux points, vous procédez ainsi :
1. Vous créez une liste contenant les différences entre les éléments des deux
vecteurs.
2. Vous élevez au carré tous les éléments du vecteur de différences.
3. Vous calculez la racine carrée des éléments additionnés.
Un simple exemple en Python consiste à partir de deux points A et B qui possèdent chacun
trois caractéristiques numériques. Si A et B décrivent deux individus, nous pouvons nous
attendre à des caractéristiques significatives telles que la taille (cm), la masse corporelle (kg)
et l’âge (ans), ce qu’exprime le code source suivant :

import numpy as np
A = np.array([165, 55, 70])
B = np.array([185, 60, 30])

Les lignes suivantes calculent les différences entre les trois éléments, procèdent à une
élévation au carré puis cherchent la racine carrée du total de ces valeurs au carré :

D = (A - B)
D = D**2
D = np.sqrt(np.sum(D))
print(D)

Voici le résultat :

45.0

La distance euclidienne n’est donc qu’une grosse addition. Le problème est que lorsque les
variables qui servent à construire le vecteur de différences sont exprimées dans des échelles
assez différentes (dans notre exemple, la taille pourrait avoir été exprimée en mètres), le
résultat est une distance anormalement influencée par les éléments ayant la plus grande
échelle. Il est donc indispensable de normaliser les échelles des variables avant d’appliquer un
algorithme à k-moyennes. Vous pouvez opter pour une plage fixe ou bien pour une
normalisation statistique avec une moyenne à zéro et une variance unitaire.
Un autre souci tient à la corrélation entre les variables qui entraîne une redondance
d’informations. En effet, lorsque deux variables sont très corrélées, une partie de ce qu’elle
signifie fait doublon. L’information est prise en compte deux fois dans le calcul de la somme qui
sert à produire la distance. Si vous n’êtes pas informé de cette corrélation, certaines variables
vont dominer le calcul de la distance de façon anormale et vous risquez de ne pas trouver les
groupes dont vous pourrez tirer profit. La solution consiste ici à éliminer cette corrélation en
appliquant d’abord un algorithme de réduction de dimensions tel que l’algorithme par
composantes principales PCA (vu dans le chapitre précédent). Il vous incombe de vérifier les
échelles et les corrélations avant d’appliquer une technique de regroupement par k-moyennes
ou autre qui produit des distances euclidiennes.

Un exemple avec des images de chiffres


Voyons comment exploiter cet outil avec des données graphiques représentant des chiffres
manuscrits. Nous allons réutiliser le jeu de tests fourni dans le paquetage Scikit-learn. Les
chiffres manuscrits sont naturellement variables dans les détails du tracé. Cette variabilité est
la marque des différences entre les personnes. Voyons comment tout d’abord importer les
données des images :

from sklearn.datasets import load_digits


digits = load_digits()
X = digits.data
ground_truth = digits.target

Après avoir importé les chiffres depuis Scikit-learn, nous affectons les données à une variable
puis stockons les labels dans une autre variable pour pouvoir vérifier plus tard. Nous pouvons
ensuite traiter les données par une technique d’analyse PCA. Voici le résultat affiché :

from sklearn.decomposition import PCA


from sklearn.preprocessing import scale
pca = PCA(n_components=30)
Cx = pca.fit_transform(scale(X))
print(‘Variance expliquée %0.3f’
% sum(pca.explained_variance_ratio_))

Voici le résultat :

Variance expliquée 0.893

Nous éliminons les problèmes d’échelle et de corrélation en appliquant une analyse PCA aux
données rééchelonnées. En théorie, la PCA peut produire le même nombre de variables qu’au
départ, mais l’exemple en élimine quelques-unes au moyen du paramètre n_components. Le
choix de n’utiliser que 30 composants, à comparer aux 64 variables initiales, permet de
préserver l’essentiel de l’information, environ 90 %, tout en simplifiant le jeu de données en
supprimant les corrélations et réduisant la redondance et le bruit conséquent.
Toute la suite du chapitre utilise le jeu de données stocké dans Cx qui a été défini dans le
précédent extrait de code source ci-dessus. Si vous avez besoin de faire exécuter un des
exemples ultérieurs directement, pensez à faire réexécuter d’abord le bloc ci-dessus pour que
la variable soit connue.
Les données issues de la transformation PCA se trouvent dans la variable nommée Cx. Nous
pouvons maintenant demander l’import de la classe KMeans puis définir les paramètres de
travail :
» n_clusters est le nombre K de centroïdes que nous voulons trouver.
» n_init est le nombre d’itérations de la technique à k-moyennes avec des centroïdes
initiaux différents. Nous avons besoin d’un nombre d’essais suffisant, par exemple 10.

from sklearn.cluster import KMeans


clustering = KMeans(n_clusters=10, n_init=10, random_state=1)
clustering.fit(Cx)

Une fois le paramétrage réalisé, notre classe de regroupement est prête à l’emploi. Nous avons
utilisé la méthode fit() en l’appliquant au jeu de données Cx pour obtenir un jeu normalisé
au niveau des échelles et avec moins de dimensions.

Recherche des solutions optimales


Nous poursuivons dans l’exemple en cours qui cherche à regrouper les données dans dix
groupes. Voyons donc ce que donne notre solution avec K = 10. Le code source suivant
compare le résultat du regroupement précédent aux valeurs empiriques (les véritables labels
des données), afin de vérifier s’il y a des correspondances.
import numpy as np
import pandas as pd
ms = np.column_stack((ground_truth,clustering.labels_))
df = pd.DataFrame(ms,
columns = [‘Données réelles’,’Clusters’])
pd.crosstab(df[‘Données réelles’], df[‘Clusters’],
margins=True)

Nous convertissons la solution, qui est stockée dans la variable interne nommée labels de la
classe clustering, vers une structure de données de pandas du type DataFrame pour pouvoir
appliquer un traitement de tableau croisé afin de comparer les labels initiaux à ceux déduits du
regroupement. Les résultats sont visibles dans la Figure 15.1. Les lignes correspondent aux
valeurs vraies, et il suffit de chercher les valeurs de regroupement pour lesquelles la majorité
des observations sont réparties dans plusieurs groupes. Elles correspondent aux images de
chiffres qui sont les plus difficiles à distinguer des autres par la méthode des k-moyennes.

Figure 15.1 : Tableau croisé des valeurs vraies et des groupes de k-moyennes.

Vous remarquez d’abord que les valeurs vraies pour 0 et pour 6 sont regroupées quasiment
dans un groupe alors que d’autres, telles que les valeurs vraies pour 3, 9 et quelques autres
entre 5 et 8 sont moins centralisées. Cette première analyse permet de deviner que certains
chiffres seront plus faciles à reconnaître que d’autres.
La technique du tableau croisé est assez pratique dans notre exemple parce qu’elle permet de
comparer le résultat du regroupement aux valeurs vraies. Cependant, il vous arrivera rarement
de disposer des valeurs vraies dans vos travaux de regroupement. Vous utiliserez dans ce cas
les centroïdes des groupes pour représenter les valeurs des variables. Vous les obtiendrez au
moyen de statistiques descriptives, en exploitant la moyenne et la médiane comme décrit dans
le Chapitre 13, pour chaque groupe. Vous pourrez ensuite comparer les statistiques
descriptives entre les groupes.
Nous pouvons par ailleurs remarquer que bien qu’il n’y ait que dix valeurs dans l’exemple, il
existe bien d’autres formes d’écritures manuscrites, ce qui demande de chercher d’autres
groupes. Le seul problème est de savoir au bout de combien de groupes différents il faut
s’arrêter.
Pour mesurer la viabilité d’un groupe, vous vous servez du concept d’inertie. L’inertie
correspond à la somme de toutes les différences entre chaque membre du groupe et son
centroïde. L’inertie est faible lorsque les exemples du groupe sont similaires aux centroïdes.
Mais cette mesure ne suffit pas seule. Vous remarquerez que l’inertie diminue lorsque vous
travaillez avec de plus en plus de groupes. Il s’agit de comparer l’inertie d’une opération de
regroupement à la précédente pour en tirer un taux de variation qui est plus facilement
mesurable et interprétable. Pour obtenir ce taux de variation d’inertie dans Python, il suffit de
mettre en place une boucle de répétition qui réalise des regroupements progressifs en
mémorisant les valeurs. Voici le code source d’une telle boucle pour notre problème de chiffres
manuscrits :

import numpy as np
inertie = list()
for k in range(1,21):
clustering = KMeans(n_clusters=k,
n_init=10, random_state=1)
clustering.fit(Cx)
inertie.append(clustering.inertia_)
delta_inertie = np.diff(inertie) * -1

Nous définissons la variable inertie dans la classe de regroupement après avoir ajusté le
regroupement. Cette variable incarne une liste qui accumule les taux de variation d’inertie
entre deux solutions. L’extrait suivant affiche un graphe en ligne de ce taux (Figure 15.2).

import matplotlib.pyplot as plt


%matplotlib inline
plt.figure()
x_range = [k for k in range(2, 21)]
plt.xticks(x_range)
plt.plot(x_range, delta_inertie, ‘ko-’)
plt.xlabel(‘Nombre de clusters’)
plt.ylabel(‘Taux de variation inertie’)
plt.show()

Figure 15.2 : Taux de variation de l’inertie pour des solutions jusqu’à k=20.

Il s’agit de chercher les sauts brusques dans l’examen de ce taux d’inertie. Si le taux monte,
cela signifie que le regroupement apporte plus de bénéfices que prévu par rapport au
précédent. S’il chute fortement, c’est que vous réalisez un regroupement de trop. Ainsi, toutes
les solutions avant la chute sont de bonnes candidates, si l’on s’en tient au principe d’économie
de moyen. En effet, un saut correspond à une augmentation de complexité, et les bonnes
solutions sont en général les plus simples (N. d. T. : loi du rasoir d’Occam). Nous pouvons
remarquer plusieurs sauts pour k=7, 9, 11 et 14, parmi lesquels k=14 semble le saut le plus
prometteur de par son important changement d’orientation par rapport à la tendance
descendante.
L’étude du taux de variation d’inertie apporte quelques indices pour trouver les bonnes
solutions de regroupement. C’est à vous de prendre la décision finale si vous avez besoin d’en
savoir encore plus au sujet des données. En revanche, si le regroupement n’est qu’une étape
dans une séquence de traitements complexe, il est inutile de passer trop de temps à
l’optimisation du nombre de regroupements. Cherchez une solution qui apporte suffisamment
de groupes pour alimenter l’algorithme d’apprentissage suivant qui va procéder à la sélection.

Regroupement de mégadonnées (big data)


La technique des k-moyennes permet donc de réduire la complexité des données en faisant un
résumé des nombreux exemples du jeu. Un problème se pose à partir du moment où le jeu de
données ne peut pas être en entier chargé dans la mémoire de travail de la machine. Une autre
solution est proposée par Scikit-learn : la variante MiniBatchKMeans qui sait effectuer des
regroupements successifs. D’ailleurs, les procédures d’apprentissage par lot (batch) travaillent
en général par blocs de données. Il n’y a que deux différences entre la fonction de k-moyennes
standard et MiniBatchKMeans :
» Vous ne pouvez pas tester plusieurs centroïdes initiaux différents sans relancer toute
l’analyse.
» L’analyse démarre si elle dispose d’un nombre de cas suffisant, la valeur par défaut étant
égale à 100. Plus le nombre de cas est important, mieux c’est ; vous réglez cela par le
paramètre batch_size.
Voyons par un exemple simple utilisant les mêmes chiffres manuscrits à quel point il est aisé
d’utiliser la classe de regroupement MiniBatchKMeans. Nous commençons par lancer un test
sur toutes les données disponibles pour obtenir l’inertie de la solution :

k = 10
clustering = KMeans(n_clusters=k,
n_init=10, random_state=1)
clustering.fit(Cx)
inertie_kmoy = clustering.inertia_
print(«Inertie de K-moyenne: %0.1f» % inertie_kmoy)

Vous apprenez que l’inertie de k-moyennes est d’environ 58250.0. Testons les mêmes données
avec le même nombre de groupes en préparant un regroupement MiniBatchKMeans pour
réaliser des lots successifs de 100 exemples :

from sklearn.cluster import MiniBatchKMeans


batch_clustering = MiniBatchKMeans(n_clusters=k,
random_state=1)
taille_lot = 100
for row in range(0, len(Cx), taille_lot):
if row+batch < len(Cx):
feed = Cx[row:row+batch,:]
else:
feed = Cx[row:,:]
batch_clustering.partial_fit(feed)
inertie_parlots = batch_clustering.score(Cx) * -1

print(«Inertie MiniBatchKmeans: %0.1f» % inertie_parlots)

Le script balaye les index du jeu de données (Cx) qui a été préalablement mis à l’échelle et
analysé en PCA afin de produire des lots de 100 observations. Avec la méthode partial_fit(),
nous ajustons un regroupement par k-moyennes pour chaque lot en nous basant sur les
centroïdes trouvés lors du tour précédent. L’algorithme s’arrête lorsqu’il n’a plus de données.
Grâce à la méthode score() appliquée à toutes les données disponibles, le programme peut
évaluer l’inertie pour une solution basée sur dix groupes, qui s’élève maintenant à
environ 64600.0. Cette technique basée sur MiniBatchKmeans présente donc une inertie
supérieure à l’algorithme standard. Cette solution est donc moins bonne et vous la réserverez
aux cas dans lesquels vous ne pouvez vraiment pas charger le jeu de données en entier en
mémoire.

Regroupements hiérarchiques
L’algorithme par k-moyennes gère des centroïdes ; le regroupement hiérarchique tente de
relier chaque point de données à son plus proche voisin en se basant sur une mesure de
distance, réalisant une agrégation (agglomération) qui produit un groupe. En répétant
l’algorithme au moyen de différentes méthodes de liaison, nous collectons tous les points de
données vers un nombre de groupes de moins en moins grand jusqu’à ce que tous les points
finissent par aboutir dans un seul groupe.
La visualisation de ce genre de processus fait penser à la classification du vivant en biologie. Il
s’agit d’une sorte d’arbre inversé dont toutes les branches convergent vers un tronc commun.
Une bonne représentation est le dendrogramme, et vous en rencontrez dans la recherche
médicale et biologique. L’implémentation proposée par Scikit-learn du regroupement par
agrégation ne permet pas de produire un tel dendrogramme à partir des données parce que
cette technique ne donne de bons résultats que dans de rares cas alors que vous avez besoin
que cela fonctionne pour de nombreux exemples.
Les algorithmes par agrégation sont moins faciles à utiliser que ceux par k-moyennes et ne
s’ajustent pas bien aux jeux de données volumineux. Ils conviennent mieux aux études
statistiques et on les rencontre souvent dans les sciences de la nature, l’archéologie (ainsi que
parfois en psychologie et économie). L’avantage est d’offrir une panoplie complète de solutions
de regroupement imbriquées : il ne vous reste plus qu’à choisir celle qui convient le mieux à
votre besoin.
Pour bien utiliser le regroupement par agrégation, il faut connaître les différentes méthodes de
liaison qui correspondent au processus de recherche, ainsi que les mesures de distance. Vous
disposez de trois méthodes de liaison (dissimilarités interclasses) :
» Distance de Ward : privilégie la recherche de groupes sphériques, à forte cohérence
interne et très différents des autres groupes. La méthode cherche en outre à trouver des
groupes de tailles similaires, ce qui est un avantage. Ne peut fonctionner qu’avec la
distance euclidienne.
» Saut maximum ou Complete : agrège les groupes à partir des observations les plus
éloignées, c’est-à-dire les points de données les moins similaires. Les groupes ainsi créés
ont tendance à contenir des observations très similaires, et sont donc assez compacts.
» Lien moyen ou Average : agrège les groupes en fonction de leurs centroïdes en ignorant
leur frontière. Produit des groupes plus vastes qu’avec la méthode précédente. Les groupes
peuvent avoir des tailles et des formes variées, à la différence de la méthode Ward. Cette
approche convient de ce fait bien en biologie, en capturant la diversité de la nature.

Vous disposez aussi de trois modes de mesure des distances :


» Euclidienne (l2) : celles déjà rencontrées pour la technique des k-moyennes.
» Manhattan (l1) : cette variante du mode euclidien calcule les distances en additionnant
les valeurs absolues des différences entre les dimensions. Si la distance euclidienne est le
plus court chemin entre deux points sur une carte, la distance Manhattan suppose d’aller
tout droit selon un axe puis selon l’autre, comme une voiture dans une ville moderne qui
contourne les pâtés de maisons.
» Cosinus : c’est le mode à choisir lorsqu’il y a trop de variables parmi lesquelles certaines
n’ont peut-être pas de signification sérieuse et sont plutôt du bruit. Les distances cosinus
réduisent ce bruit en partant du profil des variables plutôt que leur valeur. Le mode tend à
associer les observations qui ont les mêmes variables pour le maximum et le minimum,
quelle que soit la valeur réelle.

Pratique du regroupement hiérarchique


Si le jeu de données ne contient pas trop d’observations, il est toujours conseillé de tenter un
regroupement hiérarchique par agrégation en réalisant toutes les combinaisons de mode
d’agrégation ou de liaison et de mode de mesure de distances, puis comparer les résultats avec
soin. Vous ne saurez que rarement quelles sont les réponses lors d’un regroupement.
L’opération d’agrégation peut vous guider vers une autre solution valable. Vous pouvez par
exemple reproduire l’analyse précédente faite avec des k-moyennes sur des chiffres manuscrits
en utilisant le mode d’agrégation Ward avec les distances euclidiennes. Le résultat est montré
dans la Figure 15.3 :

from sklearn.cluster import AgglomerativeClustering

Hclustering = AgglomerativeClustering(n_clusters=10,
affinity=’euclidean’,
linkage=’ward’)
Hclustering.fit(Cx)

ms = np.column_stack((ground_truth,Hclustering.labels_))
df = pd.DataFrame(ms, columns = [‘Données réelles’,’Clusters’])
pd.crosstab(df[‘Données réelles’],
df[‘Clusters’], margins=True)
Figure 15.3 : Tableau croisé entre valeurs réelles et groupes par agrégation de Ward.

Pour notre exemple, les résultats sont un peu meilleurs qu’avec l’approche par k-moyennes.
Bien sûr, vous aurez remarqué qu’il faut plus de temps pour réaliser ce genre d’analyse. Si le
nombre d’observations est important, les calculs d’un regroupement hiérarchique peuvent
prendre des heures, et rendre la solution non viable. Pour contourner ce problème du temps de
traitement, vous pouvez adopter un regroupement en deux phases qui fonctionne plus vite tout
en apportant une solution hiérarchique même pour un jeu de données volumineux.

Regroupement en deux phases


Pour un regroupement en deux phases, vous commencez par traiter les observations initiales
par la méthode des k-moyennes sur un grand nombre de groupes. En pratique, vous pouvez
utiliser comme valeur la racine carrée du nombre des observations. Arrangez-vous pour que le
nombre de groupes ne dépasse pas de l’ordre de 100 à 200 pour la seconde phase qui
correspond au regroupement par agrégation. Voici un exemple qui utilise 50 groupes :

from sklearn.cluster import KMeans


clustering = KMeans(n_clusters=50,
n_init=10,
random_state=1)
clustering.fit(Cx)

Le problème à régler à ce moment est la mémorisation des liens entre les cas et les groupes
provenant du traitement par k-moyennes. Nous choisissons d’utiliser une structure de type
dictionnaire.

Kx = clustering.cluster_centers_
Kx_mapping = {case:cluster for case,
cluster in enumerate(clustering.labels_)}

Notre nouveau jeu de données correspond à la variable Kx qui contient les centroïdes des
groupes qui ont été détectés par l’algorithme à k-moyennes. Chaque groupe peut être vu
comme une synthèse correcte des données de départ. Si vous demandez un regroupement de
cette synthèse, vous devriez obtenir quasiment la même chose qu’en faisant un regroupement
des données initiales.

from sklearn.cluster import AgglomerativeClustering


Hclustering = AgglomerativeClustering(n_clusters=10,
affinity=’cosine’,
linkage=’complete’)
Hclustering.fit(Kx)

Vous pouvez alors connecter les résultats aux centroïdes dont vous vous êtes servis au départ,
ce qui permet facilement de décider si un groupe hiérarchique a bien été constitué à partir de
certains centroïdes issus de k-moyennes. Le résultat auquel vous parvenez incarne les
observations qui correspondent aux groupes de k-moyennes qui possèdent ces centroïdes ou
centres de masse.
H_mapping = {case:cluster for case,
cluster in enumerate(Hclustering.labels_)}
final_mapping = {case:H_mapping[Kx_mapping[case]]
for case in Kx_mapping}

Il ne reste plus qu’à évaluer la solution en produisant un tableau croisé comme nous l’avions
fait pour les deux algorithmes précédents par k-moyennes et par hiérarchie directe
(Figure 15.4).

ms = np.column_stack((ground_truth,
[final_mapping[n] for n in range(max(final_mapping)+1)]))
df = pd.DataFrame(ms,
columns = [‘Données réelles’,’Clusters’])
pd.crosstab(df[‘Données réelles’],
df[‘Clusters’], margins=True)

Figure 15.4 : Tableau croisé entre valeurs réelles et regroupement en deux phases.

Cette solution reste assez proche des deux précédentes. Cela prouve que l’approche est une
bonne méthode pour gérer un jeu de données volumineux, voire un jeu de mégadonnées. Vous
pouvez le réduire vers une représentation plus compacte afin de le traiter ensuite par un
regroupement moins capable de s’ajuster à un gros volume, mais offrant des techniques plus
précises et plus variées.
L’approche en deux phases possède un autre avantage, celui de bien se comporter face à des
données bruitées ou aberrantes. En effet, la première phase basée sur les k-moyennes élimine
bien ces problèmes en les renvoyant pour traitement par d’autres solutions de regroupement.

Détection de nouveaux groupes avec DBScan


Les regroupements par k-moyennes et par agglomération vont produire des groupes cohérents
ressemblant à des bulles distribuées uniformément dans toutes les directions, notamment si
vous utilisez le critère de distance de Ward. Mais la réalité peut produire des résultats
complexes et déstabilisants : les groupes peuvent revêtir des formes bien éloignées de la bulle
idéale. Le module des jeux de données de Scikit-learn (voyez un aperçu à l’adresse
http://scikit-learn.org/stable/modules/clustering.html) procure toute une gamme de
profils attrayants ; vous ne pouvez cependant pas les exploiter avec succès au moyen d’une des
méthodes par k-moyennes ou agglomération : vous y trouvez de grands cercles concentriques,
de petits cercles entrelacés et des spirales (qui font penser à un gâteau roulé, à cause de la
façon dont les points sont distribués).
Un autre algorithme de regroupement disponible porte le nom DBScan ; il part d’une intuition
assez futée qui permet de résoudre même les problèmes les plus coriaces. En effet, DBScan
suppose que les groupes sont denses et commence donc à explorer l’espace des données dans
toutes les directions en posant une frontière de groupe dès que la densité diminue. Les zones
de l’espace de données dont la densité est considérée comme insuffisante sont marquées
comme vides et tous les points qui s’y trouvent sont marqués commebruit ou plutôt comme
données aberrantes, c’est-à-dire des points qui possèdent des valeurs inhabituelles ou
étonnantes.
L’algorithme DBScan est plus complexe que celui par k-moyennes, et réclame donc plus de
temps de traitement, mais il est plus rapide que le regroupement par agglomération. Il cherche
à deviner automatiquement le nombre de groupes et marque les données étranges qui
n’entrent dans aucune des classes. C’est en cela que DBScan se distingue des autres
algorithmes qui tentent eux de forcer chacune des observations dans une classe ou une autre.
Reprenons notre exemple de chiffres manuscrits, en le reformulant avec quelques lignes de
code source Python :

from sklearn.cluster import DBSCAN


DB = DBSCAN(eps=3.7, min_samples=15)
DB.fit(Cx)

Avec DBScan, vous n’avez pas besoin de fournir un paramètre K pour le nombre de groupes
espérés car l’algorithme les trouve par lui-même. Cela semble simplifier l’utilisation de
DBScan, mais en réalité, l’algorithme vous demande en échange de définir deux paramètres
fondamentaux pour bien travailler : eps et min_sample :
» eps : distance maximale entre deux observations permettant de les considérer comme
étant du même voisinage.
» min_sample : nombre minimal d’observations d’un voisinage permettant de les convertir
en un point central (core point).

Le principe de l’algorithme est de circuler dans les données en construisant des groupes par
association de toutes les observations qui forment un voisinage. Par voisinage, on entend un
petit groupe de points de données situés les uns des autres à une distance d’au maximum eps.
Si ce nombre de points ainsi regroupés est inférieur à la valeur min_sample, DBScan ne crée
pas cet îlot de voisinage.
DBScan parvient à produire tous les voisinages quelle que soit la forme des groupes, la seule
condition étant la proximité gérée par le paramètre eps. S’il ne trouve plus de voisins dans la
portée, DBScan cherche à créer des groupes même avec seulement deux points de données
s’ils sont dans les limites de eps. Les points qui ne sont associés à aucun groupe sont
considérés comme du bruit, car trop différents pour appartenir à aucun groupe.
Vous devrez faire de nombreux essais en faisant varier les valeurs de eps et de min_sample.
Les groupes produits vont changer énormément en fonction des valeurs des deux paramètres.
Vous commencerez par un petit nombre pour min_samples afin de favoriser des
regroupements sous forme de nombreux hameaux ; choisissez par exemple la valeur 5. Faites
alors varier la valeur de eps en commençant par 0.1. Ne soyez pas déçu si vous n’obtenez pas
de résultats intéressants au départ. Continuez à essayer différentes combinaisons.
Revenons à notre exemple après cette petite description de DBScan. Lançons une exploration
des données pour obtenir une nouvelle perspective sur celles-ci. Nous commençons par
dénombrer les groupes :

from collections import Counter


print(‘Nombre de clusters: %i’ % len(np.unique(DB.labels_)))
print(Counter(DB.labels_))

ms = np.column_stack((ground_truth, DB.labels_))
df = pd.DataFrame(ms,
columns = [‘Données réelles’, ‘Clusters’])

pd.crosstab(df[‘Données réelles’],
df[‘Clusters’], margins=True)

Quasiment la moitié des observations sont regroupées dans le groupe -1 qui correspond hélas
au bruit, des valeurs que l’on peut considérer comme « asociales » . Quand on tient compte du
nombre de dimensions (ici, 30 variables décorrélées suite à une analyse PCA) et de la grande
variété des échantillons (puisque ce sont des manuscrits), il est normal que de nombreux cas
n’entrent pas naturellement dans le même groupe. Le résultat de l’exemple est visible dans la
Figure 15.5.

Nombre de clusters: 12
Counter({-1: 836, 6: 182, 0: 172, 2: 159, 1: 156, 4: 119,
5: 77, 3: 28, 10: 21, 7: 18, 8: 16, 9: 13})
Figure 15.5 : Tableau croisé du traitement par DBScan.

La force de DBScan est de fournir des groupes fiables et cohérents. En effet, DBScan n’est pas
forcé de trouver une solution avec un nombre déterminé de groupes lorsque cette solution
n’existe pas, alors que c’est le cas avec les k-moyennes et les agglomérations ou agrégations.
Chapitre 16
Détection des données aberrantes
DANS CE CHAPITRE :
» Définition d’une donnée aberrante

» Distinction entre valeurs extrêmes et nouvelles valeurs

» Capture des aberrants par statistiques simples

» Capture des aberrants difficiles par techniques avancées

L es erreurs surviennent au moment le moins opportun, y compris dans le traitement des


données. En outre, les erreurs dans les données sont difficiles à détecter, surtout s’il y a de
nombreuses variables de types très différents et à des échelles variées. Les erreurs de
données peuvent prendre la forme de données systématiquement absentes pour certaines
variables ou de valeurs erronées distribuées au hasard. Surtout, vous allez rencontrer des
données dites aberrantes (outliers). Toutes les situations suivantes sont sérieuses :
» Des valeurs absentes pour certains groupes de cas ou de variables, ce qui laisse supposer
qu’il y a une cause particulière à l’erreur.
» Des valeurs erronées dépendant de la façon dont les données ont été produites ou
traitées par l’application. Il vous faut dans ce cas savoir si les données proviennent d’un
instrument de mesure. Les conditions de l’environnement et les erreurs humaines peuvent
impacter la fiabilité des instruments.
» Enfin, un cas ou une mesure peut être valide, mais la valeur semble très éloignée de
celles correspondant à la plage attendue pour cette variable. Si vous ne pouvez pas
trouver de raison à cet écart, c’est que vous êtes face à une donnée aberrante, un
aberrant.

Parmi toutes ces erreurs, les plus ardues à corriger sont les données aberrantes parce qu’il
n’existe pas une définition unique de cet état, ni des raisons claires à leur présence au milieu
de vos données. Il vous faut donc lancer des investigations et des évaluations.
Le fichier de calepin de tous les exemples de ce chapitre correspond à P4DS4D2_16.

Détection des données aberrantes


De façon générale, une donnée aberrante (nous parlerons d’aberrant) est une donnée qui est
franchement différente, éloignée, des autres données du même échantillon. Cette distance est
incarnée par une valeur trop haute ou trop basse par rapport à la majorité des autres. Il peut
même s’agir d’une combinaison anormale de plusieurs valeurs. Si vous devez par exemple
analyser les fiches des étudiants inscrits dans une université, vous serez nécessairement attiré
par ceux qui sont bien trop jeunes ou beaucoup plus âgés que la moyenne. De même, les
étudiants qui se sont inscrits dans des branches très peu apparentées mériteront un examen
scrupuleux.
Le problème des aberrants, c’est qu’ils faussent vos distributions et ont un impact sur la
totalité des statistiques de tendance. Les moyennes sont décalées dans un sens ou dans l’autre,
ce qui influence toutes les autres mesures descriptives. Un aberrant va toujours faire croître la
variance et modifier les corrélations, ce qui vous fait tirer des conséquences incorrectes au
sujet des données et des relations entre les variables.
Voyons par un exemple très simple l’effet à courte échelle d’un seul aberrant parmi plus de
mille observations normales :

import matplotlib.pyplot as plt


plt.style.use(‘seaborn-whitegrid’)
%matplotlib inline
import numpy as np
from scipy.stats.stats import pearsonr
np.random.seed(101)
normal = np.random.normal(loc=0.0, scale= 1.0, size=1000)
print(‘Moyenne mean: %0.3f Médiane: %0.3f Variance: %0.3f’ %
(np.mean(normal), np.median(normal), np.var(normal)))

Nous utilisons le générateur aléatoire de NumPy pour créer une variable portant le nom
normal qui contient 1 000 observations provenant d’une distribution standard.
Les statistiques descriptives principales que sont la moyenne, la médiane et la variance ne
laissent rien présager de gênant. Voici ces trois valeurs :

Moyenne mean: 0.026 Médiane: 0.032 Variance: 1.109

Nous allons maintenant modifier une seule valeur afin d’insérer un aberrant :

aberrant = normal.copy()
aberrant[0] = 50.0
print(‘Moyenne mean: %0.3f Médiane: %0.3f Variance: %0.3f’ %
(np.mean(aberrant),
np.median(aberrant),
np.var(aberrant)))

print(‘Corrélation de Pearson: %0.3f p-valeur: %0.3f’ %


pearsonr(normal,aberrant))

Nous choisissons d’appeler la nouvelle variable aberrant et nous l’injectons à la position


d’index 0, là où se trouve une valeur positive égale à 50.0 dans notre structure de données.
Nous redemandons alors les mêmes statistiques :

Moyenne mean: 0.074 Médiane: 0.032 Variance: 3.597


Corrélation de Pearson: 0.619 p-valeur: 0.000

Vous constatez que la moyenne a triplé, et la variance a subi le même sort. La médiane, qui se
base sur les positions, ne varie pas puisqu’elle cherche quelle valeur occupe la position
médiane entre toutes les observations une fois celles-ci triées.
Plus grave encore, la corrélation entre variable initiale et variable aberrante est très éloignée
de +1.0 (la valeur de corrélation d’une variable par rapport à elle-même). Cela montre que la
mesure de relation linéaire entre les deux variables a été sérieusement altérée.

Autres causes probables de problèmes


Les aberrants faussent les mesures clés de vos statistiques, mais changent également la
structure des relations entre les variables. Un aberrant peut gêner un algorithme
d’apprentissage machine de deux façons :
» Les algorithmes qui travaillent sur les coefficients risquent d’utiliser un coefficient erroné
dans leur tentative de réduire leur incapacité à comprendre les cas aberrants. C’est
notamment le cas des modèles linéaires qui sont des sommes de coefficients, mais ce ne
sont pas les seuls.
» Les aberrants peuvent également impacter les systèmes d’apprentissage arborescents
tels que Adaboost ou Gradient Boosting Machines.
» Les algorithmes apprennent en puisant dans des échantillons de données. Un aberrant
peut forcer un algorithme à donner trop d’importance à la probabilité d’une valeur très
faible ou très forte, dans le cas d’une configuration donnée des variables.

Ces deux situations vont empêcher un algorithme d’apprentissage de bien généraliser pour de
nouvelles données. En d’autres termes, les aberrants forcent l’apprentissage à se sur-ajuster
au jeu de données actuel.
Plusieurs remèdes sont possibles face à des aberrants ; certains vous demandent de modifier
les données actuelles alors que d’autres vont vous faire choisir une fonction d’erreur
appropriée à votre algorithme d’apprentissage. (En effet, certains algorithmes permettent de
choisir une fonction d’erreur différente en paramètre lors de la configuration de la procédure
d’apprentissage.)
La plupart des algorithmes d’apprentissage savent exploiter plusieurs fonctions d’erreur. Ce
genre de fonction va aider l’algorithme à travailler en comprenant les erreurs et en appliquant
des ajustements dans son processus. Cela dit, certaines fonctions d’erreur sont très sensibles
aux données aberrantes alors que d’autres y résistent beaucoup mieux. C’est ainsi qu’une
mesure d’erreur élevée au carré va amplifier les aberrants du fait que les erreurs qui
découlent des exemples ayant de grandes valeurs sont élevées au carré, ce qui augmente
encore leur impact.

Distinction entre anomalies et nouvelles données


Les données aberrantes sont souvent le fruit d’erreurs et se produisent donc très rarement, ce
qui rend leur détection encore plus difficile. Ce travail est cependant essentiel pour produire
des résultats pertinents. Dans certains domaines, la détection des anomalies est le but même
de la datalogie : c’est le cas dans la détection des fraudes aux assurances et dans le système
bancaire, dans la détection des défauts de fabrication, dans les systèmes de suivi médical et
toutes autres applications critiques, de sécurité et d’alerte.
Il est indispensable de bien distinguer la présence des aberrants dans les données et l’arrivée
de nouvelles données contenant des anomalies par rapport aux cas connus. Supposez que vous
ayez par exemple passé un certain temps à purifier vos données ou à produire une application
d’apprentissage à partir d’un lot de données existant. Il est dans ce cas indispensable de
vérifier si les nouvelles données ont le même profil que les anciennes, afin d’être certain que
les algorithmes vont continuer à travailler correctement pour leurs classifications et leurs
prédictions.
Les datalogues parlent dans ce cas de détection des nouveautés, parce qu’ils ont besoin de
savoir à quel point les nouvelles données restent similaires aux anciennes. Des données
vraiment nouvelles sont considérées comme des anomalies : une nouveauté peut masquer un
événement important et risque d’empêcher un algorithme de continuer à bien fonctionner,
puisque l’apprentissage machine dépend d’abord des données avec lesquelles l’entraînement a
eu lieu. Il ne sera peut-être pas capable de se généraliser avec des cas totalement nouveaux.
Vous devez donc relancer l’entraînement de l’algorithme quand vous recevez de nouvelles
données.
L’expérience nous apprend que le monde est en mouvement permanent. Des nouveautés
apparaissent nécessairement. Vos données vont donc elles aussi changer au cours du temps, et
de manière imprévue, tant au niveau des variables cibles que des prédicteurs. Il s’agit du
phénomène nommé dérive conceptuelle (concept drift). Le mot « concept » concerne la cible et
le mot « dérive » les données source qui servent à réaliser la prédiction. Celle-ci se décale
lentement mais de façon incontrôlée, un peu comme un navire à la dérive. Nous pouvons
distinguer plusieurs dérives conceptuelles et situations de nouveauté dans l’étude d’un modèle
de datalogie :
» Physique : les systèmes de reconnaissance de visages ou de la voix et les modèles
météorologiques ne changent jamais vraiment. Il n’y a pas à craindre de nouveautés, mais
il faut rechercher les aberrants qui découlent notamment de problèmes de mesure des
données.
» Politique et économique : ces modèles changent parfois à long terme. Il faut surveiller
les effets à long terme qui débutent lentement puis se propagent et s’amplifient, finissant
par rendre vos modèles inefficaces.
» Comportements sociaux : les réseaux sociaux et le vocabulaire utilisé changent au
quotidien. Vous devez prévoir l’apparition de nouveautés ; sans cette précaution, le modèle
va se détériorer brutalement et devenir inutilisable.
» Données des moteurs de recherche, des banques et du e-commerce : ces modèles
changent assez souvent et vous devez bien vérifier l’irruption des nouveautés qui doivent
vous inviter à relancer l’entraînement pour créer un nouveau modèle et maintenir la
précision.
» Cyber-sécurité et publicité : ces modèles changent sans cesse. Il faut gérer les
nouveautés en permanence. La réutilisation des mêmes modèles est une source de
danger.

Une méthode univariée simple


Une bonne manière de commencer à chercher les aberrants, quel que soit le nombre de
variables présentes, consiste à étudier chaque variable une à une sous forme graphique et
statistique. Cela correspond à l’approche univariée ; elle permet de détecter un aberrant en
constatant une valeur de variable incongrue. Le paquetage pandas permet de trouver les
aberrants assez facilement pour deux raisons :
» une méthode describe() simple d’emploi qui vous fournit la moyenne, la variance, les
quartiles et les extrêmes des valeurs numériques de chaque variable ;
» un système de visualisation automatique en boîte à moustaches.

En combinant ces deux techniques, vous déterminez aisément s’il y a des aberrants et où il faut
les chercher. Le jeu de données de test sur le diabète tiré de Scikit-learn est un excellent
exemple pour commencer :

from sklearn.datasets import load_diabetes


diabetes = load_diabetes()
X,y = diabetes.data, diabetes.target

La troisième commande permet de rassembler toutes les données dans la variable nommée X
qui est un tableau du type ndarray de NumPy. Nous pouvons ensuite transformer cela en une
structure DataFrame et réclamer quelques statistiques descriptives (Figure 16.1) :

import pandas as pd
pd.options.display.float_format = ‘{:.2f}’.format
df = pd.DataFrame(X)
df.describe()

Figure 16.1 : Statistiques descriptives d’une structure DataFrame.

Vous repérez les variables douteuses en observant les extrémités de la distribution (la valeur
maximale d’une variable). Vous vérifiez notamment que les valeurs minimale et maximale sont
éloignées du quartile 25 et du quartile 75. Le tableau affiché laisse voir que de nombreuses
variables ont des valeurs maximales indûment élevées. La production d’une boîte à moustaches
le confirme. Le code source suivant crée cette boîte pour toutes les variables (Figure 16.2).

fig, axes = plt.subplots(nrows=1, ncols=1, figsize=(10, 5))


df.boxplot(ax=axes);

Figure 16.2 : Boîte à moustaches avec valeurs aberrantes.

La boîte à moustaches possède des moustaches positionnées à plus ou moins 1,5 fois la plage
interquartile IQR (InterQuartile Range), soit la distance entre les quartiles inférieur et
supérieur par rapport aux bords inférieur et supérieur de la boîte (les quartiles supérieur et
inférieur). Ce style de boîte à moustaches est appelé boîte de Tukey d’après le statisticien John
Tukey qui l’a inventée et diffusée parmi d’autres techniques d’exploration des données. Elle
permet de bien montrer l’existence de cas en dehors des moustaches, tous ces points peuvent
être considérés comme des données aberrantes.

Exploitation d’une distribution gaussienne


Une autre technique pour traquer les aberrants consiste à profiter de la distribution normale.
Même si les données ne sont pas encore normalisées ainsi, le fait d’appliquer une
normalisation va vous permettre d’augmenter les probabilités de trouver des valeurs
anormales. C’est ainsi que 99,7 % des valeurs que l’on trouve dans une distribution normale
standardisée doivent se trouver dans la plage entre +3 et –3 d’écart type (déviation standard)
par rapport à la moyenne, comme le montre le code source suivant :

from sklearn.preprocessing import StandardScaler


Xs = StandardScaler().fit_transform(X)
# La méthode .any(1) évite les doublons
df[(np.abs(Xs)>3).any(1)

La Figure 16.3 montre les lignes du jeu de données avec quelques probables valeurs
aberrantes.

Figure 16.3 : Visualisation de quelques probables valeurs aberrantes.

Le module Scikit-learn permet aisément de standardiser les données et même d’enregistrer


toutes les transformations pour réutilisation dans d’autres jeux. Toutes vos données peuvent
ainsi être standardisées de la même façon, qu’elles soient destinées à un entraînement
d’apprentissage machine ou à des tests de performance.
Dans une distribution normale standardisée s’applique la règle dite des 68-95-99.7. Elle stipule
que 68 % des valeurs doivent se situer dans un écart type, 95 % dans deux écarts types
et 99,7 % dans trois. Mais cette règle peut ne plus pouvoir s’appliquer lorsque les données sont
distordues. Dans ce genre de situation, vous devrez utiliser des estimations moins strictes, par
exemple selon l’inégalité de Tchebychev (Chebyshev en anglais). Selon cette inégalité, pour un
écart type k autour de la moyenne, il ne doit pas y avoir plus de 1/k^2 % de cas supérieurs à la
moyenne. En pratique, à distance de sept écarts types de la moyenne, la probabilité de trouver
une valeur valable et au mieux de 2 %, quelle que soit la distribution. Cette valeur est une
probabilité faible et vous pouvez sans hésiter considérer qu’il s’agit d’un aberrant.
N.d.T. : Selon l’inégalité de Tchebychev, les plafonds sont de 75 % pour deux écarts types et
de 89 % pour trois écarts types.

L’inégalité de Tchebychev est prudente. Une valeur située à au moins sept écarts types de la
moyenne a d’énormes probabilités d’être aberrante. Vous opterez donc pour cette règle
lorsque vous aurez impérieusement besoin de ne pas écarter une valeur en la considérant à
tort comme aberrante. Dans les autres cas, vous pouvez vous contenter de la règle habituelle
des 68-95-99.7.

Gestion des valeurs aberrantes


Une fois que vous avez détecté des aberrants, il faut les traiter. Si vous pensez que ce sont des
erreurs de saisie ou de mesure, vous pouvez supprimer ces valeurs directement. En langage
Python, vous pouvez les désélectionner par une indexation astucieuse.
Vous choisirez de ne modifier des valeurs ou de n’en exclure certaines qu’après avoir bien
compris la cause de la présence de ces aberrants. Vous pouvez sans souci éliminer les valeurs
qui sont clairement des erreurs, suite aux mesures ou aux prétraitements. Si en revanche vous
considérez les aberrants comme des valeurs légitimes, ce qui est rare, il est préférable de les
conserver en réduisant leurs valeurs relatives (si l’algorithme d’apprentissage se fonde sur les
poids relatifs des observations), ou bien choisissez d’augmenter la taille du jeu pour réduire
l’impact des aberrants.
Si nous décidons de garder ces données, nous les standardisons et pouvons plafonner les
valeurs aberrantes en appliquant un multiplicateur de l’écart type :

Xs_capped = Xs.copy()
o_idx = np.where(np.abs(Xs)>3)
Xs_capped[o_idx] = np.sign(Xs[o_idx]) * 3

Dans cet extrait, la fonction de NumPy nommée sign() permet de récupérer le signe (+1 ou –
1) de l’aberrant. La valeur est alors multipliée par trois puis affectée au point de données qui a
été récupéré par un index booléen dans le tableau standardisé.
Cette technique souffre d’une contrainte, car l’écart type sert pour les valeurs hautes et les
valeurs basses, ce qui suppose que la distribution des données est symétrique, alors que cela
se présente rarement en réalité. Vous pouvez dans ce cas utiliser une technique un peu
complexe qui réalise un plafonnement et porte le nom du chercheur Charles P. Winsor (la
« winsorisation » ). Selon cette technique, toutes les valeurs en dépassement sont forcées à
une valeur spécifiée en tant que plafond, qui est en général celle correspondant aux 5 % de la
limite inférieure et aux 95 % de la limite supérieure :

from scipy.stats.mstats import winsorize


Xs_winsorized = winsorize(Xs, limits=(0.05, 0.95))

Vous disposez ainsi de valeurs butées pour les valeurs très grandes et très petites, ce qui vous
assure de préserver l’asymétrie éventuelle de la distribution. Dans les deux cas, par
multiplication d’écarts types ou par winsorisation, les données sont ensuite prêtes à subir
d’autres traitements et analyses.
Vous disposez enfin d’une solution automatique qui consiste à laisser la librairie Scikit-learn
transformer les données en plafonnant les aberrants. Il suffit d’utiliser l’outil échelonneur
nommé RobustScaler, outil basé sur l’écart interquartile (IQR, déjà utilisé dans les boîtes à
moustaches de ce chapitre) :

from sklearn.preprocessing import RobustScaler


Xs_rescaled = RobustScaler().fit_transform(Xs)

Approche multivariée
L’étude univariée des variables une à une permet de détecter un grand nombre d’observations
aberrantes, mais tous les aberrants ne s’éloignent pas nécessairement beaucoup de la norme.
Certains sont le produit d’une combinaison inhabituelle de valeurs de plusieurs variables.
Cette situation est rare, mais son impact peut perturber sérieusement les algorithmes
d’apprentissage.
Il ne suffit plus dans ce cas d’inspecter en détail chacune des variables pour éliminer les cas
anormaux du jeu de données. Vous ne détecterez ce genre de souci qu’avec quelques
techniques capables de considérer plusieurs variables en même temps.
Ces techniques abordent le problème avec un point de vue différent pour chacune :
» réduction des dimensions ;
» regroupement par densité ;
» modélisation de distribution non linéaire.

Vous appliquerez chacune de ces techniques pour pouvoir comparer leurs résultats et détecter
des indices particuliers pour certains cas. Parfois, vous les aurez déjà aperçus par votre
analyse univariée, mais d’autres seront de nouveaux cas à résoudre.
Utilisation de l’analyse PCA
L’analyse par composantes principales PCA sait restructurer entièrement les données en
supprimant les doublons et en reclassant les nouvelles composantes en fonction de la quantité
de variance initiale exprimée. Ce genre d’analyse procure donc une vue complète et
synthétique de la distribution des données, ce qui permet de faire ressortir les aberrants
multivariés.
La distribution générale des données est rendue visible d’abord par les deux premières
composantes qui sont les plus informatives au niveau des variances. Le résultat permet
d’obtenir de bons indices des aberrants candidats.
Parallèlement, les deux dernières composantes (les résiduelles ou ultimes) regroupent toutes
les informations que la méthode PCA n’a pas pu positionner ailleurs. Elles fournissent aussi des
suggestions de candidats à l’aberrance, moins évidents à traquer.

from sklearn.decomposition import PCA


from sklearn.preprocessing import scale
from pandas.plotting import scatter_matrix
pca = PCA()
Xc = pca.fit_transform(scale(X))

first_2 = sum(pca.explained_variance_ratio_[:2]*100)
last_2 = sum(pca.explained_variance_ratio_[-2:]*100)

print(‘Variance par composantes 1 et 2: %0.1f%%’ % first_2)


print(‘Variance par composantes ultimes: %0.1f%%’ % last_2)

df = pd.DataFrame(Xc, columns=[‘comp_’ + str(j)


for j in range(10)])

fig, axes = plt.subplots(nrows=1, ncols=2,


figsize=(15, 5))
first_two = df.plot.scatter(x=’comp_0’, y=’comp_1’,
s=50, grid=True, c=’Azure’,
edgecolors=’DarkBlue’,
ax=axes[0])
last_two = df.plot.scatter(x=’comp_8’, y=’comp_9’,
s=50, grid=True, c=’Azure’,
edgecolors=’DarkBlue’,
ax=axes[1])

plt.show()

La Figure 16.4 présente deux nuages de points pour les deux premières composantes. Le
programme présente également la variance telle qu’expliquée par les deux premières
composantes (soit la moitié du contenu d’informations du jeu) ainsi que par les deux dernières
composantes :

Variance par composantes 1 et 2: 55.2%


Variance par composantes ultimes: 0.9%

Figure 16.4 : Les deux premières et les deux dernières composantes en sortie d’analyse PCA.

Étudiez particulièrement les points de données selon les axes, l’axe des x correspondant à la
variable indépendante et l’axe des y à la variable dépendante. Vous pourriez ainsi détecter un
seuil qui permettra de distinguer les données correctes de celles qui sont douteuses.
En utilisant les deux composantes ultimes, vous allez pouvoir repérer quelques points à étudier
précisément en utilisant un seuil de –0.3 pour la dixième composante et un seuil de –1.0 pour
la neuvième. Tous les cas situés entre ces deux valeurs sont des candidats à l’aberrance
(Figure 16.5).

aberrant = (Xc[:,-1] > 0.3) ¦ (Xc[:,-2] > 1.0)


df[aberrant]

Figure 16.5 : Les cas aberrants éventuels détectés par l’analyse PCA.

Regroupement par densité DBScan


Les aberrants sont des points isolés dans l’espace des variables. La technique de
regroupement par densité DBScan permet de rapprocher les éléments pour faire ressortir les
régions trop peu denses. C’est donc un outil tout à fait approprié pour explorer
automatiquement les données à la recherche d’autres aberrants.
Voici un exemple d’utilisation de DBScan pour détecter les aberrants :

from sklearn.cluster import DBSCAN


DB = DBSCAN(eps=2.5, min_samples=25)
DB.fit(Xc)

from collections import Counter


print(Counter(DB.labels_))

df[DB.labels_==-1]

Comme déjà vu dans le chapitre précédent, DBScan a besoin des deux paramètres eps et
min_samples. Pour trouver les bonnes valeurs, vous allez devoir faire des essais en réajustant
les deux paramètres, un travail assez délicat.
Vous commencez par une petite valeur pour min_samples en augmentant progressivement la
valeur de eps à partir de 0.1. Après chaque essai, vous évaluez la situation en comptant le
nombre d’observations qui se trouvent dans la classe –1 pour l’attribut labels. Vous arrêtez
d’itérer lorsque le nombre d’aberrants ainsi détectés semble suffisant pour vous lancer dans
une inspection visuelle.
Vous trouverez toujours des points de données situés sur les bordures des parties denses et il
est donc difficile de fournir un seuil pour le nombre de cas candidats à faire partie de la classe
des –1. En règle générale, les aberrants ne doivent pas constituer plus de 5 % des cas, mais ce
n’est qu’une règle générale.
Les résultats du précédent exemple vous apprennent combien d’exemples font partie du
groupe des –1, c’est-à-dire ceux que l’algorithme considère comme extérieurs au groupe
principal. Vous découvrez à cette occasion la liste des cas qui font partie de ce groupe.
Bien que ce soit moins automatique, vous pouvez également appliquer l’algorithme de
regroupement par k-moyennes pour détecter les aberrants. Vous commencez par une analyse
avec regroupement pour obtenir un nombre de groupes suffisamment important. (Si vous
n’êtes pas sûr, essayez plusieurs solutions.) Vous pouvez ensuite chercher les groupes qui ne
contiennent que quelques cas, voire un seul ; ce sont sans doute des aberrants parce que ce
sont de petits groupes à l’écart des grands groupes contenant la majorité des observations.

Détection automatique par forêt aléatoire


Nous verrons en détail dans le Chapitre 20 de puissantes techniques d’apprentissage
correspondant aux forêts aléatoires. Leur principe est de subdiviser le jeu de données en petits
ensembles d’après les valeurs de certaines variables. Il devient ainsi possible de prédire plus
facilement la classification ou la régression de chaque sous-ensemble, autrement dit, la
solution consiste à diviser pour mieux régner.
L’algorithme IsolationForest profite du fait que les aberrants sont faciles à distinguer de la
majorité des autres cas en étudiant la différence de valeurs ou combinaisons de valeurs.
L’algorithme mesure le temps requis pour séparer un cas des autres et produire un sous-
ensemble distinct. Moins il faut de temps pour faire la séparation, plus il est probable qu’il
s’agisse d’un aberrant. Pour mesurer l’effort, IsolationForest produit une distance. Plus
cette valeur est faible, plus le cas est un aberrant probable.
Une fois que vous avez mis en production vos algorithmes d’apprentissage, vous pouvez vous
servir d’un algorithme IsolationForest entraîné comme mesure de contrôle, car de
nombreux algorithmes d’apprentissage ne savent pas comment réagir aux aberrants et aux
nouveautés.
Pour détecter les aberrants avec IsolationForest, il suffit de choisir le niveau de
contamination, c’est-à-dire le pourcentage de cas qui sont considérés comme aberrants en
fonction d’une distance. Basez-vous sur votre expérience et sur le niveau de qualité que vous
supputez pour les données. Le script suivant produit une forêt IsolationForest
opérationnelle :

from sklearn.ensemble import IsolationForest


auto_detection = IsolationForest(max_samples=50,
contamination=0.05,
random_state=0)
auto_detection.fit(Xc)

evaluation = auto_detection.predict(Xc)
df[evaluation==-1]

Le résultat permet d’obtenir la liste des cas suspects. L’algorithme est entraîné à reconnaître
les cas normaux. Lorsque vous injectez de nouveaux cas dans le jeu puis les évaluez avec un
algorithme IsolationForest déjà entraîné, vous pouvez rapidement voir si ces nouvelles
données posent problème et où.
L’algorithme IsolationForest est gourmand en puissance de traitement. Si vous lancez une
analyse sur un vaste jeu de données, prévoyez beaucoup de temps et beaucoup d’espace
mémoire.
PARTIE 5
Apprendre des données

DANS CETTE PARTIE :


» Les quatre algorithmes fondamentaux
» Validation croisée, sélection et optimisation
» Techniques linéaires et non linéaires pour augmenter la complexité
» Utilisation d’ensembles de données pour obtenir de meilleurs résultats
Chapitre 17
Quatre algorithmes fondamentaux
DANS CE CHAPITRE :
» Les régressions linéaire et logistique

» Le théorème de Bayes pour la classification naïve

» Prédictions basées sur les cas similaires grâce à KNN

ette partie applicative du livre vous propose de découvrir les algorithmes et outils qui vont
C permettre d’apprendre grâce aux données, c’est-à-dire d’entraîner un modèle, pour ensuite
prédire des estimations numériques (par exemple des prix dans l’immobilier) ou des
estimations catégorielles de classe (par exemple différentes espèces de fleurs) en partant de
nouveaux exemples. Nous allons commencer par l’algorithme le plus simple pour aller vers les
plus sophistiqués. Les quatre algorithmes présentés ici constituent un point de départ
classique pour toute personne intéressée par la datalogie.
Le fichier calepin des exemples de ce chapitre correspond à PYDASC_17.

Régression linéaire et recherche de chiffres


La technique de régression est connue depuis longtemps en statistiques, car elle sert à
construire des modèles simples mais efficaces dans l’économie, la psychologie, les sciences
sociales et la politique. La régression permet aussi de tester des hypothèses pour comprendre
les différences entre les groupes et pour modéliser des problèmes complexes basés sur des
valeurs numériques ordinales, binaires et à classes multiples, mais aussi des données de
comptage et des relations hiérarchiques. L’outil de régression sert évidemment aussi beaucoup
en datalogie, et constitue même le couteau suisse de l’apprentissage machine applicable à
toutes sortes de problèmes. La régression linéaire est libérée de la plupart des propriétés
statistiques, ce qui permet au datalogue de l’utiliser comme un algorithme simple et
compréhensible, tout en étant efficace pour faire des estimations, et dans sa variante de
régression logistique, pour classifier.

ALGORITHMES SIMPLES ET COMPLEXES

Les termes simple et complexe ne correspondent pas à des situations figées en


apprentissage machine : leur sens dépend du problème à résoudre. Certains algorithmes
se résument à des additions alors que d’autres comprennent des calculs complexes et des
manipulations de données (deux activités que Python prend en charge pour vous). Toute la
différence tient aux données. Il est conseillé de tester plusieurs modèles en commençant
par les plus simples. Il vous arrivera souvent de constater qu’une solution simple est plus
efficace. Vous pouvez par exemple essayer un modèle linéaire au lieu d’une approche plus
complexe et obtenir des résultats tout à fait exploitables. C’est l’essence du slogan connu
dans le milieu selon lequel on ne rase pas gratis (« no free lunch ») : aucune approche ne
peut répondre à tous les problèmes et la solution la plus simple constitue parfois la
meilleure réponse à un problème coriace.
Le théorème « no free lunch » avait été inventé par David Wolpert et William Macready. Il
édicte que deux algorithmes d’optimisation peuvent être considérés comme équivalents
lorsque leurs performances sont similaires pour tous les problèmes possibles. Si deux
algorithmes sont équivalents en théorie, il n’est possible de prouver que l’un des deux est
meilleur que l’autre qu’en l’appliquant à un problème spécifique. Les curieux trouveront
d’autres détails dans la page http://www.no-free-lunch.org/ dans laquelle deux des
théorèmes cités sont réellement utilisés en mécapprentissage.
La famille des modèles linéaires
La relation linéaire est un modèle statistique dans lequel sont définies les relations entre une
variable cible et un jeu de caractéristiques prédictives. Elle se base sur le genre de formule
suivant :

y = bx + a

Cette formule théorique peut être réexprimée de façon plus parlante en fonction du domaine
d’application. Par exemple, vous aurez besoin d’estimer les ventes futures en fonction des
ventes passées et du budget consacré à la publicité pour ces produits. Dans ce cas, la formule
devient celle-ci :

VentesFut = B*(BudgetPub) + A

Vos souvenirs de lycée vous rappellent que la formule y = bx + a correspond en géométrie à


une ligne dans le plan orthonormé avec une abscisse et une ordonnée. La plupart des
mathématiques d’apprentissage machine sont de ce niveau et Python peut sans souci faire les
calculs pour vous.
Pour démystifier cette formule, analysons ses composants : a est la valeur de l’interception
c’est-à-dire celle de y lorsque x vaut zéro alors que b est le coefficient incarnant la pente de
la ligne droite, donc la relation entre x et y.
Lorsque b est positif, y augmente et diminue en même temps que x. Lorsque b est négatif, y
change en sens inverse de x.
Vous pouvez donc considérer b comme l’unité de changement de signe de y en fonction d’un
changement d’unité de x. Lorsque la valeur de b est proche de zéro, l’effet de x sur y est
léger alors que si b a une grande valeur positive ou négative, x a fort effet sur y.
Voilà pourquoi la régression linéaire peut trouver la meilleure solution de y = bx + a et faire
ressortir la relation entre la variable cible y et la caractéristique prédictive x. Les valeurs a
(alpha) et b (coefficient bêta) sont estimées d’après les données et leur valeur est trouvée
grâce à l’algorithme de régression linéaire, afin que la différence entre toutes les valeurs cibles
réelles y et toutes les valeurs y dérivées de la formule de régression reste la plus faible
possible.
L’expression peut être visualisée sous forme graphique en tant que somme des carrés de toutes
les distances verticales entre les points de données et la ligne de régression. Cette somme est
toujours la valeur minimale qu’il est possible de trouver en calculant la ligne de régression
correctement à partir d’une estimation qui est celle des moindres carrés ordinaires ou OLS
(Ordinary Least Square). Cette estimation est déduite de statistiques ou de l’algorithme de
gradient équivalent, c’est-à-dire une méthode d’apprentissage. Les différences entre les
valeurs réelles de y et la ligne de régression qui correspond aux valeurs prédites sont
considérées comme des résidus parce que c’est ce qu’il nous reste après la régression, des
erreurs.

Utilisation de plusieurs variables (régression


multiple)
Lorsque vous n’utilisez qu’une variable pour prédire la valeur de y, vous utilisez la régression
linéaire simple. En utilisant plusieurs variables, vous faites de la régression linéaire multiple.
L’échelle des différentes variables a peu d’importance pour obtenir des prédictions de
régression linéaire précises, mais il est conseillé de standardiser X parce que cette échelle
relative des différentes variables peut avoir un impact pour certaines variantes des traitements
de régression que nous verrons plus loin, et de plus, ce traitement vous aidera à mieux
comprendre les données dont vous comparerez les coefficients au niveau de leur impact sur y.
L’exemple suivant part du jeu de données immobilières Boston de Scikit-learn. Il cherche à
estimer les prix des maisons dans la région de Boston avec une régression linéaire et tente de
repérer les variables qui ont le plus d’impact sur le résultat. L’exemple effectue donc une
standardisation des prédicteurs.

from sklearn.datasets import load_boston


from sklearn.preprocessing import scale
boston = load_boston()
X = scale(boston.data)
y = boston.target

La classe de Scikit-learn qui permet les régressions fait partie du module linear_ model. Une
fois que vous avez ajusté l’échelle de la variable X, vous n’avez pas d’autres paramètres ni de
préparatifs à prévoir pour profiter de cet algorithme.

from sklearn.linear_model import LinearRegression


regression = LinearRegression(normalize=True)
regression.fit(X, y)

Une fois que l’algorithme a été ajusté, vous vous servez de la méthode nommée score() pour
obtenir la valeur de R2. Cette mesure entre 0 et 1 permet de savoir si un modèle de régression
en particulier réussit mieux à prédire y qu’une simple moyenne. L’opération d’ajustement
produit une ligne ou une courbe qui épouse au mieux les points de données fournis en entrée ;
vous ajustez cette ligne ou cette courbe pour pouvoir réaliser d’autres opérations, et
notamment des prédictions basées sur les tendances ou les motifs obtenus grâce à ces
données. La valeur de R2 peut également être considérée comme la quantité d’informations
cibles expliquée par le modèle (comme dans une corrélation élevée au carré) ; une valeur
proche de 1 prouve que le modèle parvient à expliquer l’essentiel de la variable y.

print(regression.score(X, y))

Voici le score qui en est issu :

0.740607742865

Dans l’exemple, la valeur de R2 est d’environ 0.74, ce qui est un bon résultat pour un modèle
simple. Ce score peut être compris comme le pourcentage d’informations encore présentes
dans la variable cible, tout en étant explicables par le modèle avec ses prédicteurs. Ce score
signifie que le modèle couvre une grande partie de l’information que vous vouliez prédire et
qu’il ne reste que 26 % inexpliqués.
En statistiques, lorsque vous utilisez des modèles linéaires, il est considéré comme acceptable
et raisonnable de calculer R2 sur le même jeu de données que celui servant à l’entraînement.
En datalogie et en apprentissage machine, il est toujours conseillé de tester les scores sur des
données qui n’ont pas servi à l’entraînement. Les algorithmes plus complexes réussissent
mieux à mémoriser les données de mécapprentissage, mais cette remarque s’avère parfois
vraie aussi pour les modèles plus simples tels que la régression linéaire.
Pour comprendre ce qui pilote les estimations dans le modèle de régression multiple, il faut
s’intéresser à l’attribut nommé coef_ ; c’est un tableau contenant les coefficients de
régression bêta. Ces coefficients sont les nombres qui ont été estimés par le modèle de
régression pour transformer en réalité les variables d’entrée de la formule vers la cible de
prédiction y. L’attribut boston.DESCR est affiché en même temps pour vous aider à savoir
quelle variable est référencée par les coefficients. La fonction zip() génère une liste itérable
des deux attributs et vous pouvez l’afficher :

print([a + ‘:’ + str(round(b, 2)) for a, b in zip(boston.feature_names,


regression.coef_,)])

Les variables indiquées avec les coefficients arrondis (les valeurs de b qui correspondent aux
pentes comme indiqué un peu plus haut) sont les suivantes :

[‘CRIM:-0.92’, ‘ZN:1.08’, ‘INDUS:0.14’, ‘CHAS:0.68’,


‘NOX:-2.06’, ‘RM:2.67’, ‘AGE:0.02’, ‘DIS:-3.1’, ‘RAD:2.66’,
‘TAX:-2.08’, ‘PTRATIO:-2.06’, ‘B:0.86’, ‘LSTAT:-3.75’]

DIS indique les distances pondérées menant à cinq agences pour l’emploi. C’est le coefficient
qui a le plus d’impact absolu. Par exemple, dans un contexte immobilier, un logement trop
éloigné des centres d’intérêt tels que les bassins d’emploi va faire baisser la valeur. En
revanche, AGE et INDUS qui décrivent l’âge du bâtiment et la disponibilité d’activités dans la
région autres que le commerce de détail n’ont pas autant d’influence sur le résultat parce que
la valeur absolue de leur coefficient bêta est inférieure à DIS.

Limitations de la régression linéaire


La régression linéaire constitue un outil simple et efficace mais qui souffre de quelques
inconvénients. Ces problèmes peuvent même annuler les bénéfices de ce genre de régression
dans certains cas, mais cela dépend surtout de la nature des données. Pour vérifier s’il y a un
problème, vous appliquez la méthode puis vous testez son efficacité. À moins d’appliquer des
traitements lourds à vos données (voyez le Chapitre 19), vous pouvez rencontrer les
contraintes suivantes :
» La régression linéaire ne peut modéliser que des données quantitatives, numériques.
Lorsque vous avez besoin de modéliser des catégories, vous devez appliquer une
régression logistique.
» Si vous ne gérez pas correctement les données manquantes, le modèle va ne plus
fonctionner. Vous devez renseigner les valeurs manquantes ou bien utiliser la valeur zéro
pour la variable de sorte à créer une variable binaire additionnelle qui permet de voir
qu’une valeur manque.
» Les données aberrantes gênent beaucoup les régressions linéaires, car celles-ci cherchent
par nature à minimiser la valeur des résidus élevés au carré, alors que les aberrants
forment de gros résidus. Cela oblige l’algorithme à se focaliser sur ces valeurs et non sur la
masse des points corrects.
» La relation entre la cible et chaque variable prédicteur ne dépend que d’un seul
coefficient. Il n’y a pas de moyen de représenter automatiquement une relation complexe
telle qu’une parabole ou une croissance exponentielle (une seule valeur de x maximise y).
Le seul moyen de modéliser une telle relation est d’utiliser des transformations
mathématiques de x et parfois de y ou d’ajouter d’autres variables. Ces deux techniques
de transformation et d’ajout de variables sont présentées dans le Chapitre 19.
» La limitation la plus importante est liée au fait que la régression linéaire produit une
somme de termes, qui peuvent varier indépendamment les uns des autres. Il est dans ce
cas difficile de prévoir comment il faut représenter l’effet de certaines variables affectant
le résultat chacune de façon très différente en fonction de la valeur. Une solution consiste à
créer des termes d’interaction ; cela revient à multiplier deux valeurs ou plus pour en créer
une nouvelle. Le problème est que cela suppose que vous savez quelle variable il faut
multiplier et créer la nouvelle variable avant de lancer la régression linéaire. Pour faire
court, vous ne pouvez pas représenter facilement des situations complexes à partir de vos
données, uniquement des situations simples.

Régressions logistiques et classes


La régression linéaire convient bien à l’estimation de valeurs, mais pas à la prédiction de
classes d’observation. Bien que la théorie en statistiques vous le déconseille, vous pouvez tout
à fait essayer de classifier une classe binaire en attribuant la valeur 1 à une classe et 0 à
l’autre. En général, le résultat est décevant, ce qui prouve que la théorie n’était pas fausse !
Rappelons que la régression linéaire travaille à partir d’une série continue d’estimations
numériques. Pour réaliser une classification correcte, il faut disposer d’une mesure mieux
adaptée, par exemple une probabilité d’appartenance à une classe. La formule suivante permet
de transformer une estimation numérique de régression linéaire en une probabilité qui sera
mieux adaptée à la description de la façon dont une classe correspond à une observation :

probabilité d’une classe = exp(r) / (1+exp(r))

La variable r correspond au résultat de la régression, c’est-à-dire à la somme des variables


pondérée par les coefficients ; exp est la fonction exponentielle (exp(r) est le nombre e de
Euler élevé à la puissance r). Lorsqu’une régression linéaire utilise une telle formule (que l’on
appelle également fonction de lien) pour transformer ces résultats en probabilité, cela
correspond à une régression logistique.

Application d’une régression logistique


La seule différence entre régression logistique et régression linéaire se situe au niveau de la
donnée y qui doit dorénavant contenir des valeurs entières désignant la classe associée à
l’observation. Si nous repartons du jeu Iris du module Scikit-learn datasets, nous pouvons
utiliser les trois valeurs 0, 1 et 2 pour noter trois classes correspondant à nos trois espèces de
fleur :

from sklearn.datasets import load_iris


iris = load_iris()
X = iris.data[:-1,:],
y = iris.target[:-1]

Pour simplifier l’utilisation de l’exemple, nous éliminons une valeur, ce qui nous permet ensuite
de l’utiliser pour tester l’efficacité du modèle de régression logistique.

from sklearn.linear_model import LogisticRegression


logistic = LogisticRegression()
logistic.fit(X, y)
single_row_pred = logistic.predict(
iris.data[-1, :].reshape(1, -1))
single_row_pred_proba = logistic.predict_proba(
iris.data[-1, :].reshape(1, -1))
print (‘Classe prédite %s, classe réelle %s’
% (single_row_pred, iris.target[-1]))
print (‘Probabilité de chaque classe entre 0 et 2: %s’
% single_row_pred_proba)

Voici le résultat affiché par le code source précédent :

Classe prédite [2], classe réelle 2


Probabilité de chaque classe entre 0 et 2:
[[0.00168787 0.28720074 0.71111138]]

La régression logistique ne se contente pas d’afficher la classe résultante qui est ici la
classe 2 ; elle procède en outre à une estimation de la probabilité que l’observation fasse partie
des trois classes. En se basant sur l’observation qui a servi à la prédiction, la régression
logistique estime une probabilité de 71 % d’appartenance à la classe 2. C’est une probabilité
forte, mais cela laisse une marge d’incertitude.
Les probabilités permettent d’estimer la classe la plus probable, mais vous pouvez également
trier les prédictions selon leur probabilité de faire partie de cette classe. Cela vous sera
particulièrement utile dans le domaine médical : le classement d’une prédiction en termes
d’éventualités par rapport à d’autres permet d’apprendre quelle maladie les patients risquent
le plus de contracter ou s’ils sont déjà atteints.

Stratégies multiclasses
La technique de régression logistique que nous venons de découvrir sait gérer
automatiquement les problèmes à classes multiples (nous avions travaillé avec trois espèces
d’iris à deviner au départ). La plupart des algorithmes de la librairie Scikit-learn qui prédisent
des probabilités ou des scores de classes savent d’office gérer des problèmes à plusieurs
classes, au moyen de deux stratégies différentes :
» Une contre toutes : l’algorithme compare chaque classe à toutes les autres, en
construisant un modèle par classe. S’il y a dix classes à trouver, vous obtenez dix modèles.
Cette stratégie se fonde sur la classe de Scikit-learn nommée OneVsRestClassifier.
» Une contre une : l’algorithme compare chaque classe contre chacune des autres
classes, afin de construire un nombre de modèles égal à n * (n-1) / 2, n correspondant
au nombre de classes. Si vous avez dix classes, vous obtenez 45 modèles puisque 10 *
(10 - 1) / 2. Cette technique se fonde sur la classe OneVsOneClassifier.

La stratégie multiclasse utilisée par défaut dans la régression logistique est Une contre toutes.
Découvrons par un exemple comment utiliser les deux stratégies avec le jeu de chiffres
manuscrits, avec des classes pour les nombres entre 0 et 9. Commençons par charger les
données et les stocker dans des variables :

from sklearn.datasets import load_digits


digits = load_digits()

train = range(0, 1700)


test = range(1700, len(digits.data))
X = digits.data[train]
y = digits.target[train]
tX = digits.data[test]
ty = digits.target[test]
Chaque observation correspond à une grille de valeurs de pixels, dont les dimensions sont
de 8 pixels sur 8. Pour faciliter l’apprentissage par l’algorithme, nous alignons les valeurs pour
obtenir 64 éléments alignés dans une liste. Nous réservons une partie des exemples pour
réaliser un test :

from sklearn.multiclass import OneVsRestClassifier


from sklearn.multiclass import OneVsOneClassifier
OVR = OneVsRestClassifier(LogisticRegression()).fit(X, y)
OVO = OneVsOneClassifier(LogisticRegression()).fit(X, y)
print(‘Précision Une contre le Reste: %.3f’ % OVR.score(tX, ty))
print(‘Précision Une contre Une: %.3f’ % OVO.score(tX, ty))

Voici les performances que permettent les deux stratégies multiclasses :

Précision Une contre Toutes: 0.938


Précision Une contre Une: 0.969

Les deux classes OneVsRestClassifier et OneVsOneClassifier travaillent en incorporant


l’estimateur qui est ici LogisticRegression. Elles fonctionnent ensuite comme n’importe quel
autre algorithme d’apprentissage de Scikit-learn. Nous constatons que la stratégie Une contre
une offre la meilleure précision, grâce à l’important nombre de modèles en concurrence.

Algorithme naïf bayésien


Qu’un algorithme puisse porter un tel nom peut surprendre. La partie « naïve » est liée à la
formule utilisée qui réalise une simplification extrême par rapport au calcul de probabilités
classique. Le mot bayésien est issu du nom du révérend Bayes qui a inventé un théorème sur la
probabilité.
Le révérend Thomas Bayes (1701-1761) était un statisticien et philosophe anglais qui a
exprimé son théorème dans la première moitié du XVIIIe siècle. Ce théorème n’a jamais été
publié de son vivant, mais a pourtant révolutionné la théorie des probabilités en introduisant le
concept de probabilité conditionnelle, plus précisément, conditionnée par l’évidence.
Commençons donc par le début, par la probabilité. La probabilité permet de connaître à quel
point un événement peut se produire, il est exprimé sous forme d’une valeur numérique
entre 0 et 1, c’est-à-dire entre 0 % et 100 %. Elle s’obtient en comptant le nombre de fois que
l’événement est survenu par rapport à tous les événements concernés. Vous pouvez donc
calculer cette probabilité à partir des données !
Supposons que vous observiez des événements, par exemple, l’existence de certaines
propriétés pour une caractéristique. Vous cherchez la probabilité d’un événement. Il suffit de
compter le nombre d’apparitions de la propriété dans les données puis de diviser cette valeur
par le nombre total d’observations. Vous obtenez une valeur entre 0 et 1 qui correspond à la
probabilité.
Lorsque vous estimez la probabilité d’un événement, vous aurez aisément tendance à croire
que vous pouvez l’appliquer à toutes les situations. Il s’agit d’un a priori qui correspond à la
première estimation de probabilité par rapport à un événement, celle qui vient à l’esprit en
premier. Vous pouvez par exemple estimer la probabilité qu’une personne inconnue soit du
sexe féminin en déclarant après avoir fait un comptage qu’elle est égale à 50 %, ce qui
correspond bien à la probabilité primaire, celle à laquelle vous allez vous attacher.
Cette probabilité a priori peut évoluer face à une évidence, autrement dit une information qui
bouleverse vos attentes. C’est ainsi que le fait évident qui pourrait faire conclure qu’une
personne est du sexe mâle ou femelle serait la longueur de sa chevelure. La propriété
« cheveux longs » aurait par exemple 35 % de probabilité dans la population globale,
mais 60 % dans la population féminine. Si le pourcentage est supérieur dans la population
féminine, en contradiction avec la probabilité générale (a priori d’avoir des longs cheveux), il
s’agit d’une information que vous vous devez d’exploiter.
Supposons maintenant que vous deviez prédire qu’une personne est homme ou femme, et que
l’évidence dont vous disposez est ses cheveux longs. Cela semble correspondre à un problème
de prédiction et cette situation est au final la même que pour la prédiction d’une variable
catégorielle à partir des données : nous possédons une variable cible avec plusieurs catégories
et vous devinez quelle est la probabilité de chaque catégorie à partir d’une évidence qui est la
donnée d’entrée. Le révérend Bayes nous avait préparé une formule très utile :

P(A¦B) = P(B¦A)*P(A) / P(B)


Cette formule ne semble pas d’accès facile, et contient du jargon statistique. Nous allons donc
l’expliquer. Servons-nous des données de l’exemple précédent pour rendre la signification de la
formule un peu plus claire :
» P(A¦B) est la probabilité de l’événement A, d’être de sexe féminin, si la chevelure est
longue (évidence B). C’est la partie de la formule qui définit ce que vous voulez prédire.
Elle équivaut à prédire y à partir de x, sachant que y est un résultat (masculin ou féminin)
et que x est une évidence (cheveux longs ou courts).
» P(B¦A) est la probabilité d’avoir des cheveux longs si on est une femme. Dans ce cas,
vous savez que la valeur est égale à 60 %. Dans tous les problèmes de données, vous
obtenez cette valeur rapidement par tableaux croisés des caractéristiques avec les
résultats cibles.
» P(A) est la probabilité d’être de sexe féminin, soit 50 % en général, un a priori.
» P(B) est la probabilité d’avoir les cheveux longs, qui est de 35 %, autre a priori.

Lorsque vous lisez des segments de la formule, par exemple P(A¦B), il faut procéder ainsi : il
s’agit de la probabilité de A sachant B. Le symbole ¦ correspond à sachant. Il s’agit dans ce
cas d’une probabilité conditionnelle parce que c’est la probabilité de survenue de A qui dépend
d’une évidence B. Dans l’exemple, si nous injectons les valeurs numériques dans la formule,
nous obtenons : 60 % * 50 % / 35 % = 85,7 %.
Dans l’exemple, même s’il n’y a que 50 % de chances qu’une personne soit de sexe féminin, le
fait de connaître l’évidence de la chevelure permet d’atteindre 85,7 %, ce qui est bien plus
favorable. Vous aurez beaucoup plus de chances de faire une bonne prédiction qu’une
personne ayant les cheveux longs soit une femme parce que vous avez un peu moins de 15 %
de risques de vous tromper.

Moins naïf que le nom le laisse croire


L’algorithme naïf bayésien applique la simple règle de Bayes en tirant profit de toutes les
évidences disponibles pour modifier la probabilité a priori de vos prédictions. Vos données
recèlent beaucoup d’évidences, si elles ont de nombreuses caractéristiques. Il est ainsi possible
de réaliser une énorme addition de toutes les probabilités qui dérivent d’une formule naïve
bayésienne simplifiée.
Nous avons vu dans la section antérieure du chapitre à propos de la régression linéaire que
produire la somme de plusieurs variables suppose que le modèle les considère comme des
éléments d’information distincts et uniques, alors que ce n’est pas vrai dans la réalité. Les
applications évoluent dans un monde interconnecté, chaque bribe d’information étant reliée à
de nombreuses autres. En utilisant plus d’une fois le même élément d’information, vous
renforcez l’impact de cet élément.
Du fait que vous ne connaissez pas ou désirez ignorer les relations entre les éléments
d’évidences, vous allez tous les faire exploiter par l’algorithme naïf bayésien. Le fait d’envoyer
tout ce que vous savez de façon naïve dans la formule fonctionne tout à fait ; de nombreuses
études ont montré d’excellentes performances, malgré la naïveté de votre hypothèse initiale.
Vous pouvez tout à fait exploiter tous les éléments pour votre prédiction, quand bien même
cela ne devrait pas pouvoir se confirmer, si l’on songe à la forte association qui relie les
variables. Voici quelques domaines d’application de cette approche naïve bayésienne :
» production de détecteurs de pourriels (spam) dans les messageries électroniques ;
» analyse de sentiments en cherchant à savoir si un texte contient des attitudes positives
ou négatives par rapport à un sujet, et pour évaluer l’état d’esprit de l’auteur ;
» travaux de traitement des textes, correcteurs grammaticaux ou estimation du langage
utilisé pour écrire ou classifier un texte dans une catégorie large.

L’approche naïve bayésienne est très prisée parce qu’elle ne requiert pas trop de données pour
fonctionner. Elle gère naturellement les classes multiples. Elle sait même gérer les variables
numériques, à condition d’appliquer quelques modifications aux variables pour les transformer
en classes. Scikit-learn propose trois classes naïves bayésiennes dans son module
sklearn.naive_bayes :
» MultinomialNB : se base sur les probabilités dérivées de la présence d’une
caractéristique. Lorsque c’est le cas, la classe affecte une certaine probabilité au résultat
indiqué par les données textuelles pour la prédiction.
» BernoulliNB : fournit la fonctionnalité multinomiale naïve bayésienne, mais en
pénalisant l’absence d’une caractéristique. La probabilité est différente selon que la
caractéristique est présente ou pas. Toutes les caractéristiques sont considérées comme
des variables dichotomes (la distribution d’une variable dichotome est une des options de
Bernoulli). Vous pouvez l’utiliser avec des données de type texte.
» GaussianNB : cette version de l’algorithme naïf bayésien suppose que toutes les
caractéristiques sont dans une distribution normale. Cette classe n’est vraiment pas idéale
pour les données textuelles contenant des mots peu denses ou dans lesquels la densité
des mots est faible (dans ce cas, mieux vaut utiliser une distribution multinomiale ou de
Bernoulli). C’est le meilleur choix en revanche si les variables ont des valeurs positives et
négatives.

Prédiction de classification de texte


L’approche naïve bayésienne est très utilisée pour catégoriser des documents. Dans les projets
liés à du texte, vous faites en général face à des millions de caractéristiques, une par mot, qu’il
soit correctement ou incorrectement orthographié. Ce texte est parfois associé entre mots
voisins sous forme de n-grammes, c’est-à-dire de séquences de mots consécutifs. L’approche
naïve bayésienne peut apprendre vite et efficacement les caractéristiques textuelles et
produire un suivi des prédictions rapidement.
Nous allons tester les capacités de classification de texte en testant tour à tour les modèles
naïfs bayésiens binomiaux et multinomiaux qui sont proposés dans Scikit-learn. Comme
données d’entrée, nous utilisons le jeu 20newsgroups déjà rencontré qui contient un grand
nombre d’articles issus de 20 groupes de discussion différents. Le jeu est divisé en un jeu
d’entraînement pour construire les modèles textuels et un jeu de test qui s’apparente au jeu
d’entraînement. Nous testerons la précision des prédictions avec ce jeu de test. Commençons
par la mise en place :

from sklearn.datasets import fetch_20newsgroups


newsgroups_train = fetch_20newsgroups(
subset=’train’, remove=(‘headers’, ‘footers’, ‘quotes’))
newsgroups_test = fetch_20newsgroups(
subset=’test’, remove=(‘headers’, ‘footers’, ‘quotes’))

Une fois les deux jeux chargés en mémoire, nous importons les deux modèles bayésiens pour
les instancier. Nous commençons par définir les valeurs alpha qui vont éviter que les
caractéristiques rares obtiennent une probabilité nulle, ce qui les exclurait de l’analyse. Nous
choisissons une toute petite valeur alpha, comme vous pouvez le voir ci-dessous :

from sklearn.naive_bayes import BernoulliNB, MultinomialNB


Bernoulli = BernoulliNB(alpha=0.01)
Multinomial = MultinomialNB(alpha=0.01)

Nous avions utilisé la technique de hachage dans le Chapitre 12 pour modéliser des données
textuelles sans craindre l’apparition de nouveaux mots après la phase d’entraînement. Deux
techniques de hachage différentes sont possibles : la première consiste à compter les mots
dans l’approche multinomiale, et l’autre se limite à noter qu’un mot est apparu ou pas dans
une variable binaire, ce qui est l’approche binomiale. Nous allons bien sûr nettoyer le jeu en
supprimant les mots vides (N. d. T. : comme dans les exemples précédents, nous nous en
tenons aux mots vides de la langue anglaise, mais revoyez le Chapitre 12 si nécessaire).

import sklearn.feature_extraction.text as txt


multinomial = txt.HashingVectorizer(stop_words=’english’,
binary=False, norm=None)
binary = txt.HashingVectorizer(stop_words=’english’,
binary=True, norm=None)

Nous pouvons maintenant lancer l’entraînement des deux classifieurs puis les tester sur le jeu
de test qui correspond à une série d’articles apparaissant à la suite de ceux du jeu
d’entraînement. Nous mesurons la précision sous forme d’un pourcentage de prédiction
correcte.

import numpy as np
target = newsgroups_train.target
target_test = newsgroups_test.target
multi_X = np.abs(
multinomial.transform(newsgroups_train.data))
multi_Xt = np.abs(
multinomial.transform(newsgroups_test.data))
bin_X = binary.transform(newsgroups_train.data)
bin_Xt = binary.transform(newsgroups_test.data)

Multinomial.fit(multi_X, target)
Bernoulli.fit(bin_X, target)

from sklearn.metrics import accuracy_score


for name, model, data in [(‘BernoulliNB’, Bernoulli, bin_Xt),
(‘MultinomialNB’, Multinomial, multi_Xt)]:
accuracy = accuracy_score(y_true=target_test,
y_pred=model.predict(data))
print (‘Précision pour %s: %.3f’ % (name, accuracy))

Voici la précision calculée pour les deux modèles naïfs bayésiens :

Précision pour BernoulliNB: 0.570


Précision pour MultinomialNB: 0.651

Vous aurez remarqué que l’entraînement des deux modèles et la génération de leurs
prédictions sur le jeu test ne prennent que peu de temps. Cela est remarquable lorsque vous
songez que le jeu d’entraînement contient plus de 11 000 articles avec 300 000 mots et que le
jeu de test en contient encore 7500 autres.

print(‘Nombre de posts dans entraînement: %i’


% len(newsgroups_train.data))
D={word:True for post in newsgroups_train.data
for word in post.split(‘ ‘)}
print(‘Nombre de mots différents dans entraînement: %i’
% len(D))
print(‘Nombre de posts dans test: %i’
% len(newsgroups_test.data))

L’exécution de ce code nous permet d’obtenir des statistiques intéressantes sur le texte :

Nombre de posts dans entraînement: 11314


Nombre de mots différents dans entraînement: 300972
Nombre de posts dans test: 7532

Méthode KNN (k plus proches voisins)


L’approche KNN (K Nearest Neighbors) ne cherche pas à construire des règles à partir des
données en exploitant des coefficients ou des probabilités. KNN recherche des similarités. En
effet, lorsqu’il s’agit de prédire une classe ou une catégorie, il peut s’avérer plus efficace de
rechercher les observations qui ressemblent le plus à celles que vous voulez classifier ou
estimer. Vous pouvez alors dériver votre réponse à partir de ces cas similaires.
La recherche des observations similaires ne suppose pas un quelconque apprentissage, mais
plutôt des mesures. Cette absence d’apprentissage de KNN l’a fait qualifier d’algorithme
paresseux (lazy) ; vous entendrez souvent cette technique décrite comme un apprentissage
basé sur une instance. Le principe consiste à supposer que des données similaires vont
normalement produire des résultats similaires. Il est essentiel de ne pas négliger les résultats
les plus directement accessibles, les fruits qui sont à portée de main, avant de s’exténuer à
grimper dans l’arbre !
La phase d’entraînement de cet algorithme est rapide parce qu’il se contente de mémoriser
des données à propos des observations. Il réalise les calculs principalement pendant les
prédictions. Lorsqu’il a trop d’observations, l’algorithme peut se voir ralenti et consommer
beaucoup d’espace mémoire. Nous déconseillons son utilisation avec d’énormes volumes de
données, car vous pourriez attendre un temps infini avant la première prédiction ! C’est un
algorithme simple et efficace qui fonctionne au mieux avec des groupes de données bien
distincts, n’ayant chacun qu’un nombre de variables pas trop important parce que c’est un
algorithme sensible au fléau des dimensions (engorgement par les dimensions trop
nombreuses).
Ce fléau des dimensions survient lorsque le nombre de variables commence à vraiment
augmenter. Prenons le cas d’une situation dans laquelle vous mesurez la distance entre les
observations ; l’espace à explorer devient de plus en plus vaste, il devient donc de plus en plus
difficile de trouver de véritables voisins, ce qui pose problème à la technique KNN. Elle peut
assez facilement confondre une observation éloignée et une observation proche. C’est un peu
comme si vous tentiez de jouer aux échecs avec un échiquier en trois dimensions. Dans un
échiquier classique, la plupart des pièces sont proches, ce qui vous permet assez facilement de
trouver les opportunités de votre prochain mouvement et les menaces pour vos pions, puisque
vous n’avez que 32 pièces et 64 positions. Imaginez maintenant que votre jeu d’échecs soit en
trois dimensions, comme c’est le cas dans certains films de science-fiction. Vos 32 pièces sont
perdues dans 512 positions possibles. Que diriez-vous maintenant d’un échiquier
à 12 dimensions ? Vous aurez beaucoup plus de mal à décider de ce qui est proche et de ce qui
est loin, et c’est ce qui peut arriver à KNN.
Vous pouvez faire en sorte que KNN retrouve ses capacités de détection des similarités entre
les observations par suppression des informations redondantes et réduction des dimensions au
moyen des techniques présentées dans le Chapitre 14.

Prédiction après observation du voisinage


Voyons un exemple d’utilisation de KNN en nous basant sur le jeu de données des chiffres
manuscrits. KNN, comme les techniques naïves bayésiennes, réussit bien à prédire un grand
nombre de classes, ou à travailler dans les situations qui vous obligeraient autrement à
construire un trop grand nombre de modèles ou à utiliser un modèle trop complexe.

from sklearn.datasets import load_digits


from sklearn.decomposition import PCA
digits = load_digits()
train = range(0, 1700)
test = range(1700, len(digits.data))
pca = PCA(n_components = 25)
pca.fit(digits.data[train])
X = pca.transform(digits.data[train])
y = digits.target[train]
tX = pca.transform(digits.data[test])
ty = digits.target[test]

Soyez averti que KNN est assez sensible aux données aberrantes. Vous devez également
ajuster les échelles des variables et supprimer les données redondantes. Dans l’exemple, nous
nous servons de l’analyse par composantes principales PCA. Ici, il n’est pas nécessaire de
rééchelonner, parce que les données correspondent à des pixels qui sont tous au même format.
Pour éviter le problème des aberrants, vous maintenez un critère de voisinage faible, c’est-à-
dire que vous n’allez pas chercher les similarités à une trop grande distance.

Vous pourrez gagner beaucoup de temps et éviter bien des erreurs si vous réussissez à
connaître le type des données. Dans l’exemple, nous savons que ce sont des matrices de pixels.
Vous appliquerez toujours comme première étape une analyse exploratoire EDA (Chapitre 13)
pour obtenir quelques informations intéressantes. Dans tous les cas, il est de bonne pratique
de toujours chercher à obtenir des informations complémentaires au sujet de la façon dont les
données ont été générées et de ce qu’elles représentent.
Pour passer au test, nous stockons nos cas dans tX, puis essayons quelques cas que KNN ne va
pas considérer comme voisins :

from sklearn.neighbors import KNeighborsClassifier


kNN = KNeighborsClassifier(n_neighbors=5, p=2)
kNN.fit(X, y)

KNN se base sur une distance pour décider qu’une observation est voisine du cas cible ou pas.
Vous réglez cette distance prédéfinie au moyen du paramètre p :
» Lorsque p vaut 2, nous utilisons la distance euclidienne qui a été décrite dans la partie sur
les regroupements du Chapitre 15.
» Lorsque p vaut 1, nous utilisons la distance de Manhattan qui est une distance absolue
entre les observations dans une grille orthonormée. Vous allez d’un point à un autre par
angles droits, comme lorsque vous marchez dans une ville moderne. La distance
euclidienne est la distance en diagonale. La distance de Manhattan n’est pas le chemin le
plus court, mais elle est plus réaliste et surtout moins sensible aux bruits et au fléau des
dimensions.

En général, la distance euclidienne est la bonne, mais elle peut donner parfois des résultats
pires que l’autre, notamment s’il y a beaucoup de variables corrélées. Notre code d’exemple
montre que l’analyse l’accepte bien.

print(‘Précision: %.3f’ % kNN.score(tX,ty) )


print(‘Prédiction: %s Réalité: %s’
% (kNN.predict(tX[-15:,:]),ty[-15:]))

Nous obtenons la précision ainsi qu’un échantillon des prédictions, ce qui permet de les
comparer avec les valeurs réelles afin de repérer les différences :

Précision: 0.990
Prédiction: [2 2 5 7 9 5 4 8 1 4 9 0 8 9 8]
Réalité : [2 2 5 7 9 5 4 8 8 4 9 0 8 9 8]

Réglages avisés du paramètre k


Un paramètre stratégique de KNN correspond à k. Lorsqu’il augmente, KNN va prendre en
compte un plus grand nombre de points dans ses prédictions, et sera donc moins influencé par
les instances correspondant à du bruit. Vos décisions vont être basées sur une moyenne à
partir d’un plus grand nombre d’observations, et seront donc encore plus pertinentes. Cela dit,
lorsque la valeur de k est trop grande, vous allez prendre en compte des voisins qui sont
vraiment trop éloignés, et qui ont donc moins de points en commun avec le cas que vous
cherchez à prédire.
Il y a un véritable arbitrage à faire. Lorsque la valeur de k est faible, vous travaillez sur un
bassin de voisins homogènes, mais risquez plus facilement de faire une erreur en considérant
que quelques cas similaires suffisent. Lorsque la valeur de k est plus importante, vous prenez
plus de cas en considération, au risque de travailler sur une trop grande portée, voire parfois
d’englober des aberrants. Si nous repartons de l’exemple précédent, nous pouvons tout à fait
ajuster la valeur de k et voir le résultat :

for k in [1, 5, 10, 50, 100, 200]:


kNN = KNeighborsClassifier(n_neighbors=k).fit(X, y)
print(‘Pour k = %3i, la précision est de %.3f’
% (k, kNN.score(tX, ty)))

L’exécution de ce code permet de voir quel est l’effet du changement de valeur de k, ce qui
permet de décider de la valeur optimale par rapport aux données disponibles :

Pour k = 1, la précision est de 0.979


Pour k = 5, la précision est de 0.990
Pour k = 10, la précision est de 0.969
Pour k = 50, la précision est de 0.959
Pour k = 100, la précision est de 0.959
Pour k = 200, la précision est de 0.907

Quelques tests vous permettent de conclure que le paramètre n_neighbors qui est celui
correspondant à k doit être réglé à la valeur 5 qui donne la meilleure précision. Vous pouvez le
régler au voisinage le plus proche avec n_neighbors=1, pour obtenir des résultats acceptables.
En revanche, toute valeur supérieure à 5 donne des résultats de moins en moins bons au
niveau classification.
Donnez-vous la règle générale suivante : si votre jeu de données ne contient pas trop
d’observations, donnez à k une valeur proche du nombre d’observations disponible élevé au
carré. Cette règle n’est cependant pas rigide. Il est toujours conseillé d’essayer plusieurs
valeurs de k pour optimiser les performances de la technique KNN. Commencez toujours par
une petite valeur et augmentez-la progressivement.
Chapitre18
Validation croisée, sélectionet optimisation
DANS CE CHAPITRE :
» Problèmes de sur- et de sous-ajustement

» Choix des bons indicateurs à surveiller

» Validation croisée des résultats

» Sélection des meilleures caractéristiques pour l’apprentissage

» Optimisation des hyperparamètres

L es
algorithmes de mécapprentissage (apprentissage machine) apprennent en étudiant les
données. Les quatre algorithmes que nous avons vus dans le précédent chapitre sont assez
simples, mais permettent néanmoins d’estimer efficacement une classe ou une valeur
lorsque vous leur fournissez des exemples avec des valeurs cibles. Un apprentissage par
induction, consistant à trouver une règle générale à partir d’exemples particuliers. Les
humains procèdent dès l’enfance à ce genre d’apprentissage par étude de quelques exemples
pour en tirer des règles générales des concepts. Il applique ensuite ces règles à de nouvelles
situations. Lorsque vous voyez quelqu’un se brûler en touchant une plaque chaude, vous
comprenez immédiatement que l’action est dangereuse, et vous évitez d’y toucher, sans avoir
besoin d’en faire l’expérience.
L’apprentissage par les exemples des algorithmes souffre de quelques pièges. Voici quelques-
uns des problèmes qui peuvent se présenter :
» Il n’y a pas assez d’exemples pour en tirer une règle, quel que soit l’algorithme machine
utilisé.
» L’application d’apprentissage est alimentée avec de mauvais exemples et ne peut donc
pas fonctionner correctement.
» Même lorsque l’application obtient des exemples corrects et suffisants, elle ne parvient
pas en produire des règles à cause d’une trop grande complexité. L’inventeur de la loi de la
gravitation, Isaac Newton, père de la physique moderne, avait expliqué que son idée de la
gravitation était venue en voyant tomber une pomme. Le problème c’est qu’il n’est pas
donné à tout monde de tirer une loi universelle à partir d’une série d’observations ; les
algorithmes ne sont pas mieux lotis que nous.

Il est crucial de garder ces problèmes à l’esprit lorsque vous abordez l’apprentissage machine.
Une telle application va généraliser plus ou moins en fonction de la quantité de données, de
leur qualité et des caractéristiques de l’application, pour faire face à de nouveaux cas. S’il y a
le moindre souci à un niveau ou à un autre, vous allez subir des contraintes sérieuses. Vous
devez reconnaître et apprendre à éviter ce genre de pièges dans vos activités de datalogie.
Le fichier contenant le code source des exemples de ce chapitre correspond à PYDASC_18.

Maîtrise des problèmes d’ajustement de modèle


Ajuster un modèle consiste à puiser dans les données pour retrouver les règles qui ont généré
ces données. D’un point de vue mathématique, cet ajustement revient à deviner une fonction
inconnue de niveau lycée, dans le style y = 4x^2 + 2x, uniquement en décortiquant les
résultats y. Vous en déduisez donc à juste titre que les algorithmes d’apprentissage produisent
en interne des formulations mathématiques pour capturer un raisonnement réel à partir des
exemples fournis.
Il n’est pas du ressort de la datalogie de prouver que ces formulations synthétiques
correspondent à la réalité. Ce qui compte, c’est qu’elles produisent des prédictions correctes.
Vous pouvez relier à des fonctions mathématiques la plupart des événements du monde
physique, mais vous ne pouvez pas en faire autant avec la dynamique des sociétés ou du
monde économique. Cela n’empêche pas les gens de tenter de faire des déductions dans ces
domaines.
En tant que datalogue, vous devez toujours chercher à trouver des visions approchées du réel,
à trouver des solutions similaires à l’effet qu’aurait une fonction inconnue, en utilisant la
meilleure qualité d’informations disponible. Les résultats de vos efforts seront jaugés selon
votre capacité à prédire des résultats spécifiques à partir de prémisses qui sont les données en
utilisant une palette d’algorithmes.
Un peu plus haut dans ce livre, nous avons vu une fonction proche de celle du monde réel,
lorsque nous avons abordé la régression linéaire. Cette formule linéaire, y = Bx + a
correspond en termes mathématiques à une ligne sur un plan. Elle permet souvent d’obtenir
une bonne approximation des données d’entraînement, même si ces données ne correspondent
pas à une ligne ou quelque chose qui s’en approche. Les autres algorithmes d’apprentissage,
en dehors de la régression linéaire, utilisent tous une formulation interne (certains comme les
réseaux neuronaux, vont même jusqu’à vous obliger à définir leur formulation totalement). La
formule de la régression linéaire est l’une des plus simples ; celle des autres algorithmes peut
devenir assez complexe. Cela dit, vous n’avez pas besoin d’en connaître les détails. Vous devez
savoir quel est le niveau de complexité, si chaque formule correspond à une ligne droite ou une
courbe, et quel est le comportement de l’algorithme face aux données aberrantes et au bruit.
Dès que vous avez besoin de faire un apprentissage à partir des données, vous devez tenir
compte de ces aspects en fonction de la formule que vous comptez utiliser :
1. Est-ce que l’algorithme d’apprentissage envisagé est le plus adapté pour
produire une approximation de la fonction inconnue que vous supposez être à
l’œuvre derrière les données en entrée ? Pour prendre une décision à ce niveau, vous
devez juger les performances de la formule de l’algorithme sur les données disponibles
pour les comparer à celles des formules d’autres algorithmes.
2. Vous devez vérifier si la formule spécifique de l’algorithme est trop simple pour
s’approcher de la fonction cachée et produire une estimation correcte, ce qui
correspond au problème du biais.
3. Vous devez enfin vérifier si la formule de l’algorithme est au contraire trop
complexe par rapport à la fonction cachée que vous devez émuler. Cela correspond
au problème de la variance.
Il n’existe aucun algorithme universel. Si vous n’avez pas assez de données, ou si les données
sont très polluées, il sera difficile à certains algorithmes d’émuler la fonction réelle cachée.

Problèmes de biais et de variance


Lorsque l’algorithme que vous avez sélectionné ne parvient pas à bien apprendre à partir des
données et semble mal fonctionner, la cause peut se situer au niveau du biais ou de la variance
dans les estimations.
» Biais : avec une formule simple, l’algorithme aura tendance à surestimer ou sous-estimer
les vraies règles qui ont produit les données. Il donne systématiquement de mauvais
résultats dans certaines situations. Un algorithme simple souffre d’un biais important ; il
possède peu de paramètres internes et ne réussit en général qu’à émuler des formules
simples.
» Variance : avec une formule complexe, l’algorithme aura tendance à collecter trop
d’informations à partir des données, et à détecter des règles qui n’existent en fait pas du
tout. Les prédictions vont devenir fantaisistes pour les nouvelles données. La variance est
un problème relatif à la mémorisation. Un algorithme complexe sait mémoriser des
caractéristiques grâce à ses nombreux paramètres internes, mais mémoriser ne signifie
pas comprendre les règles.

Le niveau de biais et de variance dépend donc de la complexité de la formule en jeu dans


l’algorithme, en relation avec la complexité de la formule cachée, supposée être à l’origine des
données observées. Face à un problème spécifique, et en puisant dans les règles de données
disponibles, vous avez intérêt à éviter une valeur élevée pour le biais et pour la variance dans
les situations suivantes :
» Vous avez peu d’observations : les algorithmes simples s’en sortent mieux, quelle que
soit la fonction cachée. Les algorithmes complexes ont tendance à trop apprendre à partir
des données, et produisent des estimations imprécises.
» Vous avez trop d’observations : un algorithme complexe va toujours réduire le niveau
de variance, parce qu’il ne peut apprendre tout ce que les données permettraient et
n’exploite que les règles, mais pas le bruit correspondant aux aberrants.
» Vous avez beaucoup de variables : si vous avez également beaucoup d’observations,
un algorithme simple aura tendance à trouver une solution approximative même pour les
fonctions cachées les plus complexes.

Stratégie de sélection d’un modèle


Lors de vos premiers pas dans la résolution d’un problème d’apprentissage, vous ne savez en
général pas grand-chose au sujet du problème, et ne pouvez pas vraiment décider quel
algorithme sera le mieux adapté. Vous ne pouvez donc pas savoir si la source d’un problème
est liée au biais ou à la variance. Considérez au départ la règle simple suivante : si un
algorithme est simple, il aura un biais élevé ; s’il est complexe, il aura une variance élevée.
Mais si vous travaillez avec des applications de datalogie réputées et bien documentées, vous
constaterez que ce qui fonctionne dans certaines situations (comme présenté dans les articles
scientifiques) ne convient pas vraiment à votre propre problème parce que les données ne sont
pas les mêmes.
Le fameux théorème de non-gratuité du mathématicien David Wolpert résume bien la
situation : deux algorithmes d’apprentissage machine sont équivalents en performances si vous
les testez sur tous les problèmes possibles. Il est impossible de déclarer qu’un algorithme est
meilleur qu’un autre. Il sera seulement meilleur pour résoudre un problème en particulier. En
d’autres termes, il n’y a de recette prédéfinie pour aucun problème ! La meilleure approche
consiste à faire de nombreux tests en vérifiant les résultats dans le cadre de procédures d’essai
scientifiquement contrôlées. Ce n’est qu’ainsi que vous serez certain que ce qui semble
fonctionner fonctionne vraiment, et surtout, fonctionnera encore avec de nouvelles données.
Vous pouvez avoir des préférences entre les différents algorithmes, mais ne pourrez jamais
savoir si tel algorithme sera le meilleur avant d’avoir essayé et comparé ses performances pour
votre problème.
Découvrons un aspect essentiel mais souvent mésestimé qu’il faut prendre en compte dans la
réussite de votre projet de données : pour ne pas vous tromper dans la sélection du meilleur
modèle, vous devez définir une façon de mesurer vos évaluations de façon à pouvoir distinguer
un bon d’un mauvais modèle, dans le contexte du problème d’entreprise ou de sciences à
résoudre. Dans certains projets, vous devez tout faire pour ne pas prédire de cas négatifs alors
qu’ils sont en réalité positifs (faux négatifs). Dans d’autres projets, vous devez absolument
détecter tous les cas positifs, et dans d’autres encore, vous pouvez vous contenter de classer
vos résultats pour que les cas positifs soient avant les négatifs, sans demander d’autres
vérifications.
En choisissant un algorithme, vous choisissez d’office un processus d’optimisation qui se fonde
sur une métrique, métrique qui informe l’algorithme de ses performances pour qu’il puisse
réajuster ses paramètres. Dans le cas d’une régression linéaire, cette métrique correspond à
l’erreur de moindres carrés, soit la distance verticale des observations par rapport à la ligne de
régression. S’agissant d’une mesure automatique, vous pouvez facilement évaluer les
performances de l’algorithme à partir de cette mécanique d’évaluation par défaut.
En dehors de la métrique par défaut, il est possible pour certains algorithmes de choisir une
fonction d’évaluation préférée. Dans d’autres cas, lorsque ce choix de fonction n’est pas
possible, vous pouvez néanmoins influencer la métrique d’évaluation en ajustant de façon
adéquate ce que l’on désigne par hyperparamètres. Cela revient à optimiser indirectement
l’algorithme par rapport à une métrique différente.
Mais avant de commencer à lancer un entraînement sur vos données pour créer des
prédictions, cherchez quelle serait la meilleure mesure de performances du projet. La librairie
Scikit-learn donne accès à toute une palette de mesures pour les problèmes de classification et
de régression. Son module nommé sklearn. metrics propose d’appeler les procédures
d’optimisation au moyen d’une simple chaîne de caractères ou par un appel à une fonction
d’erreur depuis les modules. Le Tableau 18.1 montre les métriques généralement utilisées
dans les problèmes de régression.

Tableau 18.1 : Métriques d’évaluation de régression.

Chaîne d’appel Fonction


mean_absolute_error sklearn.metrics.mean_absolute_error
mean_squared_error sklearn.metrics.mean_squared_error
r2 sklearn.metrics.r2_score

La chaîne d’appels r2 correspond à une mesure statistique de régression linéaire portant le


nom R carré. Elle permet de juger de la puissance prédictive du modèle par rapport à une
moyenne simple. Les applications d’apprentissage exploitent rarement cette mesure parce
qu’elle ne permet pas de connaître explicitement les erreurs produites par le modèle. Cela dit,
une valeur de r2 forte suppose peu d’erreurs. Les autres métriques exploitables pour les
modèles de régression correspondent aux erreurs quadratiques moyennes et aux erreurs
absolues moyennes.
Les erreurs au carré pénalisent beaucoup les valeurs extrêmes alors que les erreurs absolues
donnent le même poids à toutes les erreurs. Vous devrez arbitrer entre réduction des erreurs
sur les observations extrêmes (erreurs au carré) et réduction des erreurs pour la majorité des
observations (erreurs absolues). Vous choisirez en fonction des besoins de l’application.
Lorsque les valeurs extrêmes sont des situations critiques, vous utiliserez une mesure d’erreur
au carré. Lorsque vous avez besoin de limiter les observations normales et usuelles, comme
dans un problème de prévisions commerciales, vous opterez pour une erreur absolue moyenne
comme référence. Voyons maintenant les outils de métrologie pour les problèmes de
classification complexes (Tableau 18.2).
La mesure de justesse qui correspond à accuracy est la plus simple en classification. Elle
compte en pourcentage le nombre de prédictions correctes et tient compte du fait que
l’algorithme a trouvé la bonne classe. Elle s’applique aussi bien aux problèmes binaires que
multiclasses. C’est une simple mesure, mais le fait d’optimiser la précision peut entraîner un
problème s’il y a un déséquilibre entre les classes.

Tableau 18.2 : Métriques d’évaluation de classification.

Chaîne d’appel Fonction


accuracy sklearn.metrics.accuracy_score
precision sklearn.metrics.precision_score
recall sklearn.metrics.recall_score
f1 sklearn.metrics.f1_score
roc_auc sklearn.metrics.roc_auc_score

Le problème peut être lié à une classe trop fréquente ou prépondérante. Par exemple dans la
détection des fraudes, la plupart des transactions sont légitimes et seules quelques-unes sont
criminelles. Un algorithme optimisé pour la précision va favoriser la classe prépondérante et
sera donc fausse dans ses prédictions pour les classes mineures, ce qui est absolument
inacceptable pour un algorithme qui doit deviner toutes les classes correctement, et non
seulement certaines.
Les problèmes que vous ne pouvez pas gérer par la justesse peuvent l’être par les mesures de
précision precision et de rappel recall ainsi que par leur optimisation conjointe avec f1. La
précision concernée ici est celle des prédictions. La mesure compte le nombre de fois où
l’algorithme a bien prédit la bonne classe. Vous vous servirez de la mesure de précision pour
diagnostiquer des cancers après avoir évalué les données des examens. La précision
correspondant au pourcentage de patients qui ont réellement un cancer, par rapport à ceux qui
ont été diagnostiqués avec un cancer. Si vous avez diagnostiqué 10 patients malades alors
que 9 seulement sont vraiment atteints, la précision est de 90 %.
N. d. T. : Nous avons traduit accuracy par justesse et conservé précision pour precision.

Les conséquences ne sont bien sûr pas les mêmes selon que vous diagnostiquez un cancer chez
un patient qui n’en a pas (faux positif) ou manquez l’évaluation d’un patient qui a un cancer
(faux négatif). La précision ne constitue qu’une partie de l’histoire. Diagnostiquer comme sain
un patient qui doit en réalité être au plus vite soigné pour un cancer est un vrai problème. La
seconde partie de l’histoire correspond à la mesure de rappel recall. Pour toute une classe,
elle indique le pourcentage de prédictions correctes. En conservant le même exemple, la
mesure de rappel indique le pourcentage de patients qui ont été correctement diagnostiqués
comme atteints. S’il y avait 20 patients cancéreux et que vous en avez diagnostiqué 9, le rappel
est de 45 %.
Il peut arriver que vous ayez une bonne précision avec un faible niveau de rappel ou un fort
niveau de rappel tout en perdant de la précision. Vous pouvez tenter de maximiser les deux
mesures (précision et rappel) au moyen du score nommé f1 qui se base sur la formule
suivante :

F1 = 2 * (precision * recall) / (precision + recall)

Grâce au score f1, vous êtes certain de toujours disposer de la meilleure combinaison de
précision et de rappel.
La mesure de performances nommée ROC AUC qui est une fonction d’efficacité de récepteur
avec aire sous la courbe (Receiver Operating Characteristic Area Under Curve) permet de trier
les classifications en fonction de leur probabilité de vraisemblance. Dans notre exemple
précédent, l’algorithme d’apprentissage optimise la mesure ROC AUC en cherchant à trier les
patients en commençant par ceux qui sont le plus susceptibles d’être malades. La mesure ROC
AUC donne une valeur élevée lorsque l’ordre de tri est correct, et une valeur faible dans le cas
contraire. Lorsque votre modèle possède une valeur ROC AUC élevée, vous pouvez
immédiatement vous concentrer sur les patients les plus susceptibles d’être affectés. Dans un
projet de détection de fraude financière, vous voudrez trier les clients dans l’ordre décroissant
de leur risque de commettre une fraude. Si votre modèle possède un ROC AUC correct, vous
pouvez immédiatement vous concentrer sur les clients les plus risqués.

Répartition entre jeu d’entraînement et jeu de


test
Après avoir vu comment choisir les mesures d’erreur les plus efficaces pour la classification et
la régression, il s’agit ensuite, pour trouver le meilleur modèle, de tester puis d’évaluer les
solutions disponibles en vérifiant leur capacité à se généraliser pour de nouveaux cas. Des
procédures efficaces pour tester des algorithmes d’apprentissage sont disponibles dans le jeu
de données d’essai Boston datant des années 1970. Ce jeu, déjà utilisé dans le chapitre
précédent, permet de travailler sur les prix de l’immobilier dans la région de Boston, avec des
paramètres sur chaque bien et chaque zone résidentielle. Commençons par charger ce jeu :

from sklearn.datasets import load_boston


boston = load_boston()
X, y = boston.data, boston.target
print(X.shape, y.shape)

Le jeu comporte plus de 500 observations avec 13 caractéristiques. La cible est un prix ; nous
optons donc pour une régression linéaire et optimisons le résultat par la méthode des moindres
carrés. Il reste à vérifier que la régression linéaire convient bien au jeu de données Boston puis
à quantifier son niveau d’adéquation avec la méthode des moindres carrés (nous pourrons ainsi
comparer à d’autres modèles) :

from sklearn.linear_model import LinearRegression


from sklearn.metrics import mean_squared_error
regression = LinearRegression()
regression.fit(X,y)
print(‘Erreur moindres carrés: %.2f’ % mean_squared_error(
y_true=y, y_pred=regression.predict(X)))

La valeur de l’erreur des moindres carrés calculée est la suivante :

Erreur moindres carrés: 21.90

Nous avons ajusté le modèle aux données par la méthode fit(). (Ce sont les données
d’entraînement qui alimentent le modèle en exemples.) Nous utilisons ensuite la fonction
mean_squared_error() pour calculer l’erreur de prédiction. La valeur obtenue, de l’ordre
de 21.90, semble correcte, mais elle a été calculée sur le jeu d’entraînement. Nous ne savons
donc pas si les choses se passeront aussi bien avec de nouvelles données (les algorithmes
d’apprentissage savent bien apprendre et bien mémoriser à partir d’exemples).
Il nous faut donc lancer un test avec des données que l’algorithme n’a jamais traitées pour
écarter toute possibilité de mémorisation. Ce n’est qu’ainsi que nous pourrons savoir si
l’algorithme sait traiter n’importe quelles nouvelles données. Nous allons donc attendre de
nouvelles données, faire des prédictions puis les confronter à la réalité. L’opération peut
prendre un certain temps, et donc représenter des risques et des coûts, tout dépendant du
type de problème à résoudre. (Certaines applications dans le domaine médical sont très
sensibles parce que des vies dépendent des conclusions.)
Par bonheur, nous avons une autre solution pour atteindre le même objectif : nous pouvons
simuler de nouvelles données en divisant en deux les observations, avec un sous-jeu
d’entraînement et un sous-jeu de test. Une pratique courante consiste à réserver de 25 à 30 %
du volume de données au jeu qui va servir à tester l’algorithme, le reste correspondant au jeu
d’entraînement. Voici comment réaliser un tel partitionnement avec Python :

from sklearn.model_selection import train_test_split


X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state=5)
print(X_train.shape, X_test.shape)

Nous obtenons en résultat le profil des deux jeux obtenus suite à la séparation. Le jeu
d’entraînement comporte 70 % des données initiales et le jeu de test en compte 30 % :

(354, 13) (152, 13)

Les deux jeux sont stockés dans les variables nommées X et y grâce à la fonction
train_test_split(). La proportion réservée au jeu de test est indiquée par le paramètre
test_size. Le contenu du jeu de test est toujours choisi au hasard par la fonction. Nous
pouvons ensuite nous servir du jeu d’entraînement :

regression.fit(X_train,y_train)
print(‘Erreur Entraînement moindres carrés: %.2f’ % mean_squared_error(
y_true=y_train, y_pred=regression.predict(X_train)))

Nous obtenons la valeur de l’erreur des moindres carrés du jeu d’entraînement :

Erreur Entraînement moindres carrés: 19.07

Après ajustement, nous arrivons à une valeur de 19.07, inférieure à celle du précédent essai.
Mais ce qui nous intéresse est le taux d’erreur du jeu de test :

print(‘Erreur Test moindres carrés: %.2f’ % mean_squared_error(


y_true=y_test, y_pred=regression.predict(X_test)))

Nous obtenons une valeur bien supérieure pour le test :

Erreur Test moindres carrés: 30.70

Notre mesure d’erreur est de 30.70 ! L’estimation sur le jeu d’entraînement était trop
optimiste. La valeur pour le jeu de test est plus réaliste, mais nous travaillons avec un volume
de données inférieur (moins de la moitié). Le résultat va évidemment changer si vous modifiez
les proportions. Voyons l’impact d’une retouche :

from sklearn.model_selection import train_test_split


X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.30, random_state=6)
regression.fit(X_train,y_train)
print(‘Erreur Entraînement moindres carrés: %.2f’ % mean_squared_error(
y_true=y_train, y_pred=regression.predict(X_train)))
print(‘Erreur Test moindres carrés: %.2f’ % mean_squared_error(
y_true=y_test, y_pred=regression.predict(X_test)))

La mesure d’erreur devient celle-ci :

Erreur Entraînement moindres carrés: 19.48


Erreur Test moindres carrés: 28.33

Ce petit exercice vous donne une idée des problèmes à prendre en compte avec les
algorithmes d’apprentissage. Chaque algorithme se caractérise par un certain niveau de biais
ou de variance dans sa prédiction des résultats. Votre problème est que vous ne pouvez pas en
estimer précisément l’impact. Lorsque vous devez choisir l’algorithme, vous ne pouvez pas être
certain de pouvoir prendre la décision la plus efficace.
Il est toujours déconseillé d’utiliser les données d’entraînement pour évaluer les performances
d’un algorithme parce qu’il va naturellement mieux réussir avec ces données qu’il connaît déjà.
C’est encore plus le cas avec les algorithmes qui ont un biais faible, en raison de la complexité.
Vous pouvez vous attendre à un faible taux d’erreur sur les données d’entraînement, ce qui
donne un avantage incongru à cet algorithme lorsque vous allez le comparer à d’autres qui ont
un profil biais/variance différent. En travaillant avec les données de test, vous réduisez le
volume d’exemples de l’entraînement, ce qui peut faire baisser les performances de
l’algorithme, mais vous obtenez en échange une estimation d’erreur plus fiable et plus
pertinente pour vos comparaisons.

Validation croisée
Lorsque le jeu de test produit à répétition des résultats instables à cause de la façon dont les
valeurs sont échantillonnées, une solution consiste à prélever plusieurs jeux de test pour en
produire une moyenne. Il s’agit d’une approche statistique qui est à la base de la technique de
validation croisée. En voici la recette simple :
1. Vous segmentez vos données en plusieurs plis (folds), chacun contenant la
même distribution de cas. En général, prévoyez 10 cas par pli ; mais vous pouvez choisir
une valeur de 3, 5 ou même 20.
2. Gardez un des plis comme jeu de test, tous les autres serviront de jeux
d’entraînement.
3. Lancez l’entraînement puis notez le résultat de votre premier jeu de test. Si vous
n’avez pas beaucoup de données, utilisez un grand nombre de plis, car le volume de
données et le nombre de plis ont tous deux un effet positif sur la qualité de l’entraînement.
4. Répétez les étapes 2 et 3 en choisissant à chaque fois un autre pli comme jeu de
test.
5. Calculez la moyenne et l’écart type de tous les résultats de tests. Vous aurez ainsi
une bonne estimation de la qualité du prédicteur. L’écart type vous donnera sa fiabilité. (Si
la valeur est trop élevée, une erreur de validation croisée pourrait se montrer imprécise.)
Attendez-vous à ce que les prédicteurs à variance élevée montrent un écart type de
validation croisée élevé.
Cette technique peut sembler un peu complexe, mais Scikit-learn vous facilite le travail grâce
aux fonctions de son module sklearn.model_selection.

Validation croisée sur k-plis (k-fold)


Pour réaliser une validation croisée, il faut d’abord initialiser un itérateur. Celui qui
implémente la validation croisée par k-plis porte le nom KFold. Il en existe d’autres dans le
module sklearn.model_selection, la plupart étant le fruit de la pratique des statistiques. Le
plus utilisé en datalogie est KFold.
Pour configurer KFold, vous devez préciser le nombre de plis à générer par le paramètre
n_splits et indiquer si vous voulez que les données soient mélangées au moyen du paramètre
shuffle. Plus la variance attendue est élevée, plus il est bénéfique d’augmenter le nombre de
plis afin d’améliorer le calcul de moyenne. Il est également conseillé de demander de rebattre
les cartes (mélanger les données), parce que des données prétriées peuvent perturber le
processus d’apprentissage de certains algorithmes lorsque les premières observations sont
très différentes des dernières.
Une fois que vous avez configuré KFold, vous pouvez appeler la fonction cross_ val_score()
qui va produire un tableau de résultats contenant un score de la fonction de même nom pour
chaque pli de validation. Vous devez alimenter la fonction avec vos données en X et y, votre
estimateur (la classe de régression) et dans le paramètre cv l’itérateur KFold que vous avez
instancié. Vous obtenez vos résultats en quelques secondes ou minutes, selon le nombre de plis
et le volume de données.
Il ne reste plus qu’à produire une estimation de moyenne et éventuellement calculer l’écart
type pour connaître le niveau de stabilité de cette moyenne :

from sklearn.model_selection import cross_val_score, KFold


import numpy as np
crossvalidation = KFold(n_splits=10, shuffle=True, random_state=1)
scores = cross_val_score(regression, X, y,
scoring=’neg_mean_squared_error’, cv=crossvalidation, n_jobs=1)
print(‘Plis: %i, Erreur moindres carrés: %.2f Ecart-type: %.2f’ %
(len(scores),np.mean(np.abs(scores)),np.std(scores)))
Voici donc notre résultat :

Plis: 10, Erreur moindres carrés: 23.76 Ecart-type: 12.13

La validation croisée peut profiter du parallélisme et de plusieurs processeurs parce qu’aucune


estimation ne dépend d’aucune autre. Pour profiter d’un processeur multicœur, vous amenez le
paramètre njobs à la valeur -1.

Stratification des échantillons pour les données


complexes
Les plis d’une validation croisée sont constitués par échantillonnage aléatoire, mais il est
parfois indispensable de savoir si et dans quelles proportions une caractéristique est présente
dans les plis d’entraînement et de test, afin d’éviter des échantillons distordus. Par exemple, le
jeu Boston possède une variable binaire (1 ou 0) pour signifier que le bien est situé le long de
l’agréable Charles River. Cette information a beaucoup d’impact sur la valeur du bien et son
attractivité. Nous pouvons visualiser l’effet de cette variable par l’extrait suivant :

%matplotlib inline
import pandas as pd
df = pd.DataFrame(X, columns=boston.feature_names)
df[‘target’] = y
df.boxplot(‘target’, by=’CHAS’, return_type=’axes’);

La boîte à moustaches de la Figure 18.1 confirme que les maisons situées le long de la rivière
se vendent plus cher que les autres. Bien sûr, il y a des maisons à prix élevé dans tout Boston,
mais il est important de surveiller combien de maisons en bordure de rivière font partie de
votre analyse parce que le modèle doit être général pour la totalité de la ville de Boston, et non
limité au cas particulier des biens situés le long de la Charles River.

Figure 18.1 : Boîte à moustaches des résultats groupés selon CHAS.

Lorsqu’une caractéristique est rare ou à fort impact, vous ne pouvez pas a priori savoir si elle
est présente dans un échantillon puisque les plis sont créés de façon aléatoire. Si la
caractéristique est présente en trop forte ou trop faible quantité dans le pli, l’algorithme risque
de déduire des règles incorrectes.
Pour vous éviter de faire travailler vos algorithmes avec des échantillons malformés lors de vos
validations croisées, exploitez la classe nommée StratifiedKFold. Elle sait contrôler
l’échantillonnage de sorte que certaines caractéristiques, ou même certains résultats (lorsque
les classes cibles sont très déséquilibrées) seront toujours présents dans les plis et dans la
bonne proportion. Vous vous contentez d’indiquer quelles variables vous voulez contrôler au
moyen du paramètre y, ce que montre l’exemple suivant :

from sklearn.model_selection import StratifiedShuffleSplit


from sklearn.metrics import mean_squared_error
strata = StratifiedShuffleSplit(n_splits=3,
test_size=0.35,
random_state=0)
scores = list()
for train_index, test_index in strata.split(X, X[:,3]):
X_train, X_test = X[train_index], X[test_index]
y_train, y_test = y[train_index], y[test_index]
regression.fit(X_train, y_train)
scores.append(mean_squared_error(y_true=y_test,
y_pred=regression.predict(X_test)))
print(‘%i plis cv Erreur moindres carrés: %.2f Ecart-type: %.2f’ %
(len(scores),np.mean(np.abs(scores)),np.std(scores)))

Voici le résultat de la validation croisée stratifiée en trois plis :

3 plis cv Erreur moindres carrés: 24.30 Ecart-type: 3.99

L’erreur de validation reste la même, mais en contrôlant la variable CHAR, l’écart type
diminue, ce qui vous confirme que la variable avait un impact sur les résultats de validation
croisée précédents.

Stratégie de sélection des variables


Bien choisir ses variables peut bénéficier au processus d’apprentissage en réduisant le niveau
de bruit qui influence les estimations (l’information inutile). La sélection des variables va donc
réduire la variance des prédictions. Plusieurs techniques sont disponibles pour ne considérer
que les variables utiles à l’entraînement et écarter celles qui sont redondantes :
» Approche univariée : sélection des variables les plus concernées par le résultat visé.
» Approche arrière ou avide : ne conserve que les variables que vous ne pouvez pas
écarter du processus d’apprentissage sans altérer les performances.

Sélection par mesures univariées


Vous pouvez ne sélectionner qu’un pourcentage des caractéristiques les mieux associées à un
résultat au moyen de la classe SelectPercentile. Voici les métriques disponibles pour
l’association :
» f_regression : mesure basée sur les performances d’une régression linéaire qui ne sert
que lorsque les cibles sont numériques.
» f_classif : mesure basée sur un test d’analyse de variance ANOVA ; ne sert que pour les
cibles catégorielles.
» chi2 : applique la statistique du Khi carré aux cibles catégorielles ; moins sensible aux
relations non linéaires entre la variable prédictive et sa cible.

Lorsque votre sélection concerne un problème de classification, les deux approches f_classif
et chi2 ont tendance à produire le même jeu de variables prépondérantes. Il reste néanmoins
conseillé de tester les deux métriques pour réaliser vos sélections.

La classe SelectPercentile permet d’appliquer directement une sélection à partir des


associations ayant les plus forts percentiles, mais elle peut aussi trier les meilleures variables.
Cela peut vous aider à choisir à partir de quel percentile vous voulez exclure une
caractéristique du processus d’apprentissage. La classe nommée SelectKBest, au
fonctionnement similaire, sélectionne les k variables du dessus du panier, k étant un nombre
et non un pourcentage :

from sklearn.feature_selection import SelectPercentile


from sklearn.feature_selection import f_regression
Selector_f = SelectPercentile(f_regression, percentile=25)
Selector_f.fit(X, y)
for n,s in zip(boston.feature_names,Selector_f.scores_):
print(‘F-score: %3.2f\t pour la caractéristique %s ‘ % (s,n))

Au bout de quelques itérations, nous obtenons les résultats suivants :

F-score: 88.15 pour la caractéristique CRIM


F-score: 75.26 pour la caractéristique ZN
F-score: 153.95 pour la caractéristique INDUS
F-score: 15.97 pour la caractéristique CHAS
F-score: 112.59 pour la caractéristique NOX
F-score: 471.85 pour la caractéristique RM
F-score: 83.48 pour la caractéristique AGE
F-score: 33.58 pour la caractéristique DIS
F-score: 85.91 pour la caractéristique RAD
F-score: 141.76 pour la caractéristique TAX
F-score: 175.11 pour la caractéristique PTRATIO
F-score: 63.05 pour la caractéristique B
F-score: 601.62 pour la caractéristique LSTAT

Dans ce résultat, un F-score (ou F-mesure) élevé indique une association plus forte entre une
caractéristique et la variable cible. La lecture de ces valeurs permet de sélectionner les
variables les plus importantes pour le modèle d’apprentissage. Soyez cependant à l’affût pour
les aspects suivants :
» Une variable à forte association peut aussi être fortement corrélée, ce qui indique une
duplication d’informations, qui a l’effet d’un bruit dans l’apprentissage.
» Certaines variables peuvent être injustement pénalisées, notamment les variables
binaires qui valent 1 ou 0. Vous remarquez par exemple que nos résultats indiquent que la
variable binaire CHAS est la moins bien associée à la variable cible, alors que nous savons
par nos essais précédents qu’elle a eu un impact lors de la phase de validation croisée.

Ce processus de sélection univariée offre un vrai avantage lorsque le nombre de variables est
important et que toutes les autres méthodes s’avèrent inutilisables en pratique. La meilleure
approche consiste à réduire la valeur de SelectPercentile d’au moins la moitié du nombre de
variables disponibles puis de réduire le nombre de variables jusqu’à une quantité gérable afin
de permettre l’utilisation d’une méthode plus sophistiquée, plus précise, telle que la sélection
avide que nous découvrons maintenant.

Approche de sélection arrière ou avide (greedy)


Lors d’une sélection univariée, c’est à vous de choisir le nom des variables à conserver.
L’approche par sélection avide réduit le nombre de caractéristiques que va utiliser le modèle
d’apprentissage en évaluant leur contribution réelle aux performances, mesurée par le taux
d’erreur. La classe RFECV effectue un ajustement et peut produire des informations concernant
un certain nombre de caractéristiques, vous en informer puis transformer automatiquement les
données X avec la méthode du même nom pour aboutir à un jeu de variables limité, comme le
montre cet exemple :

from sklearn.feature_selection import RFECV


selector = RFECV(estimator=regression,
cv=10,
scoring=’neg_mean_squared_error’)
selector.fit(X, y)
print(«Nombre optimal de caractéristiques: %d»
% selector.n_features_)

Cet exemple présente un nombre de caractéristiques optimal pour notre problème :

Nombre optimal de caractéristiques: 6

Vous pouvez récupérer un index vers ce jeu de variables optimal en utilisant l’attribut
support_ de la classe RFECV après ajustement :

print(boston.feature_names[selector.support_])

Cette commande affiche la liste des caractéristiques retenues :

[‘CHAS’ ‘NOX’ ‘RM’ ‘DIS’ ‘PTRATIO’ ‘LSTAT’]

Vous constatez avec joie que CHAS apparaît dorénavant parmi les caractéristiques les plus
influentes, ce qui n’était pas le cas de la recherche univariée par l’approche précédente. RFECV
peut détecter qu’une variable est importante, qu’elle soit binaire, catégorielle ou numérique.
C’est tout simplement parce que cette méthode évalue directement le rôle que joue la
caractéristique dans la prédiction.
La méthode RFECV est certainement plus efficace que l’approche univariée puisqu’elle tient
compte des caractéristiques à forte corrélation ; elle est en outre réglée pour optimiser la
mesure de l’évaluation (qui n’est en général, ni chi2, ni F-score). Mais c’est un processus avide,
qui réclame donc beaucoup de puissance de traitement, et ne peut donner qu’une
approximation du meilleur ensemble de prédicteurs.
Comme tous les algorithmes d’apprentissage, RFECV risque de sur-ajuster la sélection. Vous
devez tester la méthode RFECV sur différents échantillons du jeu d’entraînement pour
confirmer quelles sont les meilleures variables à sélectionner.

Optimisation des hyperparamètres


Pour clore ce chapitre, intéressons-nous aux techniques permettant de trouver le meilleur
réglage des hyperparamètres d’un algorithme d’apprentissage, ce qui garantira les meilleures
performances de prédiction. L’essentiel des performances d’un algorithme sont déjà définies à
deux niveaux :
» choix de l’algorithme : aucun algorithme d’apprentissage ne convient à tous les types
de données. Il est indispensable de choisir le plus approprié à chaque projet.
» sélection des bonnes variables : les performances de la prédiction dépendent
énormément de la création des caractéristiques (les variables nouvellement créées sont
plus prédictives que les anciennes) et par la sélection des caractéristiques (par suppression
des doublons et du bruit).

Votre prédiction sera encore plus apte à être généralisée et vos résultats bonifiés si vous
peaufinez vos hyperparamètres, notamment avec les algorithmes complexes dont le
fonctionnement n’est pas formidable si vous vous en tenez au paramétrage par défaut.
Le terme hyperparamètre désigne un paramètre que vous devez vous-même choisir et régler,
car l’algorithme ne peut pas les ajuster par lui-même à partir des données. Comme toutes les
autres décisions qui doivent être prises par le datalogue dans un processus d’apprentissage, il
vous faut choisir avec tact, après avoir étudié les résultats de la validation croisée.
Le module de Scikit-learn nommé sklearn.grid_search est conçu pour optimiser les
hyperparamètres. Il réunit plusieurs utilitaires qui automatisent et simplifient la recherche des
meilleures valeurs pour chacun d’eux. Le code source des prochains paragraphes présente les
procédures appropriées, et nous commençons par charger en mémoire le jeu de données des
fleurs Iris :

import numpy as np
from sklearn.datasets import load_iris
iris = load_iris()
X, y = iris.data, iris.target

Une fois le jeu d’iris et la librairie NumPy en place, nous pouvons passer à l’optimisation de
l’algorithme pour bien prédire les espèces d’iris.

Recherche combinatoire systématique (grid)


La façon la plus évidente de chercher les meilleurs hyperparamètres d’un algorithme consiste
à tous les tester puis à choisir la meilleure combinaison. Dans un paramétrage complexe avec
donc plusieurs paramètres, il y a des centaines, voire des milliers de tests à faire, en
produisant des modèles variant légèrement l’un de l’autre. La recherche combinatoire (grid
search) est une méthode systématique qui tente toutes les combinaisons d’hyperparamètres
vus comme des jeux individuels. Cette technique coûteuse en temps représente un des
meilleurs moyens d’optimiser une application pour laquelle de nombreuses combinaisons de
fonctionnement sont possibles, alors qu’une seule est la meilleure. Il faut savoir que les
hyperparamètres pour lesquels de nombreuses solutions sont possibles (des minimas locaux)
peuvent facilement vous laisser croire que vous avez trouvé la meilleure solution, alors qu’une
marge d’amélioration existe encore.
La recherche combinatoire ressemble à un filet de pêcheur : au départ, mieux vaut utiliser un
filet à larges mailles, car cela permet de repérer les bancs de poissons. Dans une deuxième
phase, vous utilisez un filet à mailles plus serrées pour capturer individuellement les poissons
aux endroits que vous avez repérés. Dans une recherche combinatoire, vous commencez par
chercher quelques valeurs éparses. Une fois que vous avez ainsi repéré les bancs de poissons,
c’est-à-dire les valeurs d’hyperparamètres à explorer plus avant, vous lancez une recherche
plus soutenue. Vous limitez ainsi les risques de sur-ajustement par validation croisée d’un trop
grand nombre de variables. Il y en a fait un principe général dans l’apprentissage machine et
dans les expériences scientifiques qui dit ceci : plus vous tentez quelque chose, plus vous
risquez de faire surgir des faux positifs.
La recherche combinatoire peut facilement être parallélisée parce que les résultats de chaque
combinaison d’hyperparamètres sont indépendants des autres. Pour profiter d’un ordinateur à
processeur multicœur, vous réglez le paramètre n_jobs à la valeur -1 au moment d’instancier
une classe de recherche combinatoire de Scikit-learn.
La recherche combinatoire n’est pas la seule technique envisageable. La librairie Scikit-learn
propose également un algorithme de recherche aléatoire, que nous décrivons brièvement en
fin de chapitre. D’autres techniques d’optimisation sont disponibles, et se fondent sur
l’optimisation bayésienne ou sur une optimisation non linéaire telle que la méthode Nelder-
Mead. Ces procédures ne sont pas disponibles dans les paquetages de datalogie que nous
utilisons avec Python dans ce livre.
Pour montrer comment exploiter efficacement une recherche combinatoire, nous allons nous
servir d’un des algorithmes simples déjà rencontrés, le classificateur par k-moyennes :

from sklearn.neighbors import KNeighborsClassifier


classifier = KNeighborsClassifier(n_neighbors=5,
weights=’uniform’, metric= ‘minkowski’, p=2)

Ce classificateur possède quelques hyperparamètres qu’un réglage permet d’optimiser :


» le nombre de voisins à prendre en compte dans l’estimation ;
» la façon de pondérer chacun des points ;
» la métrique à utiliser pour trouver les voisins.

En considérant une plage de valeurs pour chacun des paramètres, vous devinez aisément que
cela correspond au test d’un assez grand nombre de modèles, 40 dans notre cas :

grid = {‘n_neighbors’: range(1,11),


‘weights’: [‘uniform’, ‘distance’], ‘p’: [1,2]}
print (‘Nombre de modèles testés: %i’
% np.prod([len(grid[element]) for element in grid]))
score_metric = ‘accuracy’

Le code multiplie le nombre de paramètres testés et affiche le résultat :

Nombre de modèles testés: 40

Pour préparer vos recherches, il vous faut construire un dictionnaire Python dont les clés sont
les noms des paramètres et les valeurs les listes de valeurs à tester. Notre exemple définit une
plage entre 1 et 10 pour l’hyperparamètre n_neighbors en spécifiant l’itérateur range(1,11)
qui va produire la séquence de nombres pendant la recherche.
Avant de démarrer, il reste à évaluer le score de la validation croisée à partir d’un modèle
témoin qui possède les paramètres par défaut suivants :

from sklearn.model_selection import cross_val_score


print(‘Témoin (baseline) avec paramètres par défaut: %.3f’
% np.mean(cross_val_score(classifier, X, y,
cv=10, scoring=score_metric, n_jobs=1)))

Vous devez noter le résultat pour pouvoir juger de l’amélioration apportée par le réglage des
paramètres :

Témoin (baseline) avec paramètres par défaut: 0.967

L’exemple commence par tester le témoin grâce à la mesure de précision (le pourcentage de
réponses exactes). Ce témoin incarne les hyperparamètres par défaut de l’algorithme (tel
qu’expliqué lors de l’instanciation de la variable classifier dans sa classe). Il n’est pas aisé
d’améliorer la précision qui est déjà ici de 0.967 (96,7 %). Nous avons trouvé la solution en
passant par une validation croisée à dix plis :

from sklearn.model_selection import GridSearchCV


search = GridSearchCV(estimator=classifier,
param_grid=grid,
scoring=score_metric,
n_jobs=1,
refit=True,
return_train_score=True,
cv=10)
search.fit(X,y)

L’instanciation est faite avec l’algorithme, le dictionnaire de recherche, la mesure et les plis de
validation croisée. La classe GridSearch travaille ensuite avec la méthode fit(). La
recherche étant terminée, il est possible de réajuster le modèle avec la meilleure combinaison
de paramètres trouvée, si vous spécifiez refit=True. Vous pouvez ainsi immédiatement lancer
une prédiction en utilisant directement la classe GridSearch. Nous allons bien sûr afficher les
meilleurs paramètres et le score que procure la meilleure combinaison :

print(‘Meilleurs paramètres: %s’ % search.best_params_)


print(‘Précision CV des meilleurs paramètres: %.3f’ %
search.best_score_)

Voici les valeurs affichées :

Meilleurs paramètres: {‘n_neighbors’: 9, ‘p’: 1, ‘weights’: ‘uniform’}


Précision CV des meilleurs paramètres: 0.973

Les résultats sont fournis par les attributs best_params_ et best_score_. La précision atteinte
vaut 0.973, ce qui est une amélioration par rapport au témoin. Nous pouvons aussi inspecter la
séquence complète de scores de validation croisée ainsi que l’écart type :

print(search.cv_results_)

Le grand nombre de combinaisons testées montre que quelques-unes ont obtenu le score
de 0.973, lorsque les combinaisons avaient neuf ou dix voisins. Vous pouvez profiter d’une
classe de visualisation de Scikit afin de mieux comprendre comment l’optimisation a travaillé
en relation avec le nombre de voisins. La méthode validation_curve() donne tous détails au
sujet du comportement des méthodes train() et validation() en fonction des
hyperparamètres différents dans n_neighbors.

from sklearn.model_selection import validation_curve


model = KNeighborsClassifier(weights=’uniform’,
metric= ‘minkowski’, p=1)
train, test = validation_curve(model, X, y,
param_name=’n_neighbors’,
param_range=range(1, 11),
cv=10, scoring=’accuracy’,
n_jobs=1)

La classe validation_curve propose deux tableaux qui contiennent les résultats avec les
valeurs des paramètres en ligne et les plis de validation croisée en colonne.

import matplotlib.pyplot as plt


mean_train = np.mean(train,axis=1)
mean_test = np.mean(test,axis=1)
plt.plot(range(1,11), mean_train,’ro--’, label=’Entrain.’)
plt.plot(range(1,11), mean_test,’bD-.’, label=’CV’)
plt.grid()
plt.xlabel(‘Nombre de voisins’)
plt.ylabel(‘Précision’)
plt.legend(loc=’upper right’, numpoints= 1)
plt.show()

L’action de projeter les lignes génère une visualisation graphique (Figure 18.2). Vous
comprenez ainsi mieux comment se déroule le processus d’apprentissage.
Figure 18.2 : Affichage des courbes de validation.

Ce graphique permet de recueillir deux éléments d’information :


1. La précision maximale en validation croisée avec 9 voisins est supérieure au
score du témoin d’entraînement. Ce dernier devrait toujours être meilleur que
n’importe quel score de validation croisée. La situation constatée confirme que l’exemple a
sur-ajusté la validation croisée ; l’obtention d’un si bon score de validation croisée est en
partie le fruit de la chance.
2. Le second pic de précision pour la valeur de cinq voisins n’est pas éloigné des
résultats les plus bas. En général, les bons scores sont dans la même région que les
valeurs optimales ; ce pic est donc suspect.
À la lecture de ces courbes, vous avez intérêt à accepter la solution à neuf voisins : c’est la
meilleure et elle est entourée d’autres solutions acceptables. Cela dit, cette solution est proche
des limites de la recherche et vous pourriez donc lancer une nouvelle recherche combinatoire
en demandant d’accepter un plus grand nombre de voisins (plus de 10), afin de vérifier si la
précision se stabilise, décroît ou augmente.
Interroger, tester et interroger encore font partie du lot quotidien des activités du datalogue.
Python et ses paquetages fournissent un bon nombre de processus automatisés pour
apprendre, apprendre et découvrir vos données. C’est à vous de poser les bonnes questions,
puis à vous encore de vérifier que les réponses sont les meilleures, en utilisant des tests
statistiques et des visualisations.

Recherches aléatoires
La recherche combinatoire est exhaustive mais prend beaucoup de temps. Elle a tendance à
sur-ajuster les plis de validation croisée si vous n’avez pas assez d’observations dans vos
données, et cherchez de façon extensive une optimisation. Notre approche correspond à la
recherche aléatoire dans laquelle vous définissez une recherche combinatoire, mais ne testez
que certaines des combinaisons choisies au hasard.
Vous pourriez croire que cela revient à tâtonner dans le noir. En fait, ce genre de recherche est
assez utile de par son inefficacité ! Si vous choisissez un assez grand nombre de combinaisons
aléatoires, vous aurez statistiquement beaucoup de chances de trouver une combinaison de
paramètres optimale, sans cette fois-ci risquer un sur-ajustement. Dans l’exemple précédent,
nous avons fait tester 40 modèles systématiquement. Avec une recherche aléatoire, nous
pouvons réduire de 75 % ce volume (10 modèles seulement) et atteindre le même niveau
d’optimisation.
La recherche aléatoire est simple à mettre en place. Vous commencez par importer la classe
depuis le module grid_search, plus vous réutilisez les paramètres de suite GridSearchCV.
Vous y ajoutez un paramètre n_iter qui décide du nom de combinaisons à essayer. Vous
pouvez en règle générale conserver un quart à un tiers du nombre total de combinaisons
d’hyperparamètres :

print(‘Meilleurs paramètres: %s’ % random_search.best_params_)


print(‘Précision CV des meilleurs paramètres: %.3f’ %
random_search.best_score_)
Une fois que vous avez réalisé la recherche, vous pouvez explorer les résultats en affichant les
meilleurs scores et paramètres :

print(‘Meilleurs paramètres: %s’ % random_search.best_params_)


print(‘Précision CV des meilleurs paramètres: %.3f’ %
random_search.best_score_)

L’affichage nous permet d’apprendre que la recherche aléatoire fournit des résultats similaires
à ceux d’une recherche systématique réclamant beaucoup de puissance machine.
Chapitre 19
Vers plus de complexité linéaire et non linéaire
DANS CE CHAPITRE :
» Renforcement polynomial des caractéristiques

» Régularisation des modèles

» Exploitation des mégadonnées (big data)

» Séparateurs à vaste marge et réseaux neuronaux

D ans les précédents chapitres, nous avons découvert certains des algorithmes
d’apprentissage les plus simples, mais assez efficaces, parmi lesquels la régression linéaire
et la régression logistique, la méthode naïve bayésienne et la technique des plus proches
voisins KNN. La connaissance de ces techniques suffit pour mener à bien un projet de
régression ou de classification. Nous pouvons maintenant partir à la découverte de techniques
encore plus complexes et puissantes : renforcement des caractéristiques des données,
amélioration des estimations par une régularisation et exploitation des mégadonnées en les
répartissant en partitions plus facilement gérables.
Nous verrons également une famille d’algorithmes très puissante pour la classification et la
régression : les séparateurs à vastes marges ou machines à vecteurs de supports SVM. Nous
ne négligerons pas une présentation des réseaux neuronaux. Ces deux techniques permettent
de résoudre les problèmes de datalogie les plus ardus. Les machines SVM avaient pris
récemment le dessus sur les réseaux neuronaux, mais ces derniers ainsi que les ensembles
arborescents ont repris l’avantage en tant qu’outils de prédiction incontournables (nous y
reviendrons dans le Chapitre 20). Les réseaux neuronaux existent depuis longtemps, mais ce
n’est que depuis quelques années qu’ils se sont améliorés au point de devenir des outils
incontournables pour les prédictions de données de type image ou texte. Nous consacrons un
certain nombre de pages aux machines SVM, en raison de la complexité des opérations de
régression et de classification avec des techniques évoluées et nous en consacrons quelques-
unes aux réseaux neuronaux. Vous n’hésiterez pas à approfondir vos connaissances dans ces
deux domaines ; vos efforts seront largement récompensés.
Le fichier calepin contenant les exemples de ce chapitre correspond à PYDASC_19. Deux
représentations graphiques concernant les algorithmes des machines SVM correspondent au
fichier PYDASC_19_SVM.

Transformations non linéaires


Les modèles linéaires (au sens strict et logistique) sont en fait des combinaisons qui effectuent
des sommes de caractéristiques pondérées en fonction des coefficients appris. Proposant un
modèle simple mais efficace, ils assurent une approximation suffisante de la complexité réelle.
Ils sont impactés par une valeur de biais importante, mais les coefficients peuvent être
améliorés en partant d’un nombre d’observations important, ce qui rend ces algorithmes plus
compétitifs que les algorithmes plus complexes.
Il reste possible d’en améliorer le fonctionnement si vous effectuez un prétraitement par
l’approche d’analyse de données exploratoire EDA. Une fois cette analyse faite, vous pouvez
encore transformer et améliorer les caractéristiques existantes de plusieurs façons :
» Linéarisation des relations entre les caractéristiques et la variable cible en appliquant des
transformations pour augmenter la corrélation afin que le nuage de points prenne un
aspect plus proche d’une ligne.
» Production d’interactions entre les variables par multiplication, afin de mieux représenter
les comportements conjugués.
» Expansion des variables par développement de polynôme afin de représenter les relations
de façon plus réaliste (par exemple, recherche de courbes de point idéal, lorsqu’un pic
correspond à un maximum de la variable, comme dans une parabole).
Transformation des variables
La façon la plus efficace de démontrer le genre de transformation que vous pouvez appliquer à
des données pour améliorer un modèle linéaire consiste à passer par un exemple. Celui de
cette section, ainsi que celui des deux suivantes, est le jeu de données immobilières de la ville
de Boston, déjà connu. Le problème doit être géré par régression ; au départ, les données
comportent dix variables qui expliquent les prix immobiliers de Boston dans les années 1970.
Les données sont triées, mais fort heureusement, la plupart des algorithmes ne sont pas
perturbés par cet ordre implicite, car ils exploitent les données de façon globale. En revanche,
lorsqu’un algorithme travaille de façon progressive, l’ordre initial peut gêner la bonne
construction du modèle. Nous pallions cela par un rebattage des cartes. Nous utilisons la
méthode seed() pour choisir une séquence prédéfinie de valeurs aléatoires puis la méthode
shuffle() (toutes deux du paquetage random) pour mélanger les index. Notre jeu de données
est ainsi réindexé :

from sklearn.datasets import load_boston


import random
from random import shuffle

boston = load_boston()
random.seed(0) # Creates a replicable shuffling
new_index = list(range(boston.data.shape[0]))
shuffle(new_index) # shuffling the index
X, y = boston.data[new_index], boston.target[new_index]
print(X.shape, y.shape, boston.feature_names)

L’appel de méthode random.seed(0) génère une logique de mélange répétable. L’appel


shuffle(new_index) génère le nouvel index mélangé. Nous affichons ensuite les profils de X
et y ainsi que la liste des noms des variables de la base :

(506, 13) (506,)


[‘CRIM’ ‘ZN’ ‘INDUS’ ‘CHAS’ ‘NOX’ ‘RM’ ‘AGE’ ‘DIS’ ‘RAD’ ‘TAX’ ‘PTRATIO’ ‘B’
‘LSTAT’]

N. d. T. : La description des différentes variables du jeu de données Boston est disponible au


moyen de la commande : print(boston.DESCR). Nous fournissons une version française de
cette description dans un fichier fourni avec les exemples qui porte le nom BostonDescr.txt.

Pour faciliter l’exploration et le traitement des données qui vont suivre, nous convertissons le
tableau de prédicteurs et la variable cible dans une structure de données DataFrame de
pandas. Normalement, Scikit-learn doit être alimenté par une structure de tableau ndarray,
mais les objets DataFrame sont acceptés également :

import pandas as pd
df = pd.DataFrame(X,columns=boston.feature_names)
df[‘target’] = y

La façon la plus efficace de détecter les transformations consiste à générer une représentation
graphique en nuage de points. Cela permet de bien voir évoluer deux variables. L’objectif est
de rendre les plus linéaires possible les relations entre prédicteurs et résultat. Nous allons
donc tenter plusieurs combinaisons, par exemple, celle-ci :

ax = df.plot(kind=’scatter’, x=’LSTAT’, y=’target’, c=’b’)

La Figure 19.1 montre le nuage de points correspondant. Nous constatons que nous pouvons
appliquer une courbe comme approximation du nuage de points, et non une ligne droite.
Lorsque LSTAT vaut environ 5, il semble que la cible prenne des valeurs entre 20 et 50. Les
excursions diminuent jusqu’à environ 10 lorsque LSTAT augmente.
Figure 19.1 : Relations non linéaires entre la variable LSTAT et les prix cibles.

Une transformation logarithmique pourrait nous aider dans un tel contexte, mais vos valeurs
doivent se situer entre 0 et 1, comme des pourcentages, ce que montre l’exemple. Dans les
autres cas, vous pouvez appliquer quelques autres transformations à votre variable x, et
notamment : x**2, x**3, 1/x, 1/x**2, 1/x**3 et sqrt(x). Il s’agit de les essayer et de
tester le résultat. Pour ce test, vous pouvez vous inspirer du script suivant :

import numpy as np
from sklearn.feature_selection import f_regression
single_variable = df[‘LSTAT’].values.reshape(-1, 1)
F, pval = f_regression(single_variable, y)
print(‘F score de la caractéristique originale : %.1f’ % F)
F, pval = f_regression(np.log(single_variable),y)
print(‘F score de la caractéristique transformée: %.1f’ % F)

Cet exemple affiche la F-mesure (score) qui permet de juger à quel point une caractéristique
prédit une solution d’un problème d’apprentissage, aussi bien une caractéristique d’origine
qu’une ayant été transformée. Nous constatons que le score de la caractéristique transformée
apporte une vraie amélioration.

F score de la caractéristique originale : 601.6


F score de la caractéristique transformée: 1000.2

Cette mesure F est très utile pour sélectionner les variables. Elle permet aussi de vérifier
l’intérêt de faire une transformation. En effet, aussi bien f_regres-sion que f_classif se
basent sur un modèle linéaire et sont donc sensibles à toute transformation réelle qui permet
de rendre les relations d’une variable plus linéaires.

Création d’interactions entre variables


Dans une combinaison linéaire, le modèle va réagir aux variations d’une variable de façon
indépendante des changements des autres variables. En termes statistiques, cela correspond
au modèle des effets propres (main effects).
Le classifieur naïf bayésien effectue le même genre d’hypothèses et fonctionne lui aussi
correctement pour des projets de textes complexes.

Le mécapprentissage travaille par approximation ; en disposant d’un jeu de variables


indépendantes, vous pouvez en général rendre vos prédictions efficaces, mais il arrive qu’il
vous manque une partie cruciale de l’image globale. Vous corrigez cette situation en décrivant
la variation de la cible en association avec la variation conjuguée d’au moins deux variables, et
ceci par deux techniques simples et directes :
» Connaissance du domaine actuel du problème. Dans le marché automobile, un
moteur bruyant constitue un point faible pour un véhicule familial, mais un avantage pour
une voiture sportive (celui qui en acquiert veut faire entendre rugir son coûteux bijou). Si
vous connaissez les préférences des clients, vous pouvez modéliser une variable pour le
niveau de bruit acceptable ainsi qu’une variable de type de véhicule pour en tirer des
prédictions exactes à partir d’un modèle analytique qui prédit la valeur du véhicule en
fonction de ses caractéristiques.
» Test de combinaison entre différentes variables. La réalisation de tests de groupe
permet de voir l’impact de certaines variables sur la variable cible. Sans rien connaître des
moteurs bruyants et des voitures de sport, vous pouvez ainsi obtenir une moyenne de
préférence différente dans l’analyse du jeu de données selon le type de véhicule et le
niveau de bruit.

Voyons donc comment tester et détecter les interactions dans notre jeu de test Boston. Nous
commençons par charger quelques classes de travail :

from sklearn.linear_model import LinearRegression


from sklearn.model_selection import cross_val_score, KFold
regression = LinearRegression(normalize=True)
crossvalidation = KFold(n_splits=10, shuffle=True, random_state=1)

Nous réinitialisons la structure DataFrame de pandas uniquement avec les variables


prédicteurs. Nous entrons ensuite dans une grande boucle for qui confronte les différents
prédicteurs pour produire une nouvelle variable contenant chacune des interactions. En
termes mathématiques, cette interaction correspond tout simplement à une multiplication :

df = pd.DataFrame(X,columns=boston.feature_names)
baseline = np.mean(cross_val_score(regression, df, y,
scoring=’r2’,
cv=crossvalidation))
interactions = list()
for var_A in boston.feature_names:
for var_B in boston.feature_names:
if var_A > var_B:
df[‘interaction’] = df[var_A] * df[var_B]
cv = cross_val_score(regression, df, y,
scoring=’r2’,
cv=crossvalidation)
score = round(np.mean(cv), 3)
if score > baseline:
interactions.append((var_A, var_B, score))
print(‘Témoin (baseline) R2: %.3f’ % baseline)
print(‘Top 10 des interactions: %s’ % sorted(interactions,
key=lambda x :x[2],
reverse=True)[:10])

Nous affichons d’abord le score du témoin (baseline) R2 de la régression, puis les


10 interactions les plus fortes, celles dont l’addition au modèle améliore le score :

Témoin (baseline) R2: 0.716


Top 10 des interactions: [(‘RM’, ‘LSTAT’, 0.79), (‘TAX’, ‘RM’, 0.782),
(‘RM’, ‘RAD’, 0.778), (‘RM’, ‘PTRATIO’, 0.766), (‘RM’, ‘INDUS’, 0.76),
(‘RM’, ‘NOX’, 0.747), (‘RM’, ‘AGE’, 0.742), (‘RM’, ‘B’, 0.738), (‘RM’,
‘DIS’, 0.736), (‘ZN’, ‘RM’, 0.73)]

Cet exemple teste l’effet de l’addition de chaque interaction au modèle en utilisant une
validation croisée à 10 plis. (Nous avons vu dans le Chapitre 18 comment exploiter les
validations croisées et les plis.) Le code mémorise les changements de la mesure de R2 dans
une pile qui est une simple liste, permettant à l’application de trier et d’analyser ces résultats.
Le score du témoin (baseline) est de 0.699 ; la pile d’interaction montre une amélioration
sensible avec une valeur de 0.782. Il est essentiel de savoir ce qui est à l’origine de cette
amélioration. Les deux variables concernées sont RM (le nombre de pièces moyens, rooms) et
LSTAT (le pourcentage de la population à faibles revenus, lower). Un nuage de points va
permettre de visualiser l’impact de ces deux variables :

colors = [‘b’ if v > np.mean(y) else ‘r’ for v in y]


scatter = df.plot(kind=’scatter’, x=’RM’, y=’LSTAT’, c=colors)

La Figure 19.2 clarifie l’amélioration. Une partie des biens situés au centre du diagramme
oblige à connaître les deux variables LSTAT et RM afin de pouvoir correctement distinguer les
biens à prix élevés des biens bon marché. C’est pourquoi une interaction est indispensable.
Figure 19.2 : La combinaison des variables LSTAT et RM aide à distinguer les biens chers des biens bon marché.

En ajoutant ce genre d’interaction et des variables transformées, nous parvenons à un modèle


de régression linéaire étendu, une régression polynomiale. Tout bon datalogue multiplie les
tests et les expérimentations pour valider l’approche de résolution d’un problème. Le code qui
suit modifie légèrement le précédent en redéfinissant l’ensemble de prédicteurs au moyen
d’interactions et de termes quadratiques (élévation des variables au carré) :

# Ajout de caractéristiques polynomiales (x**2,y**2,xy)


polyX = pd.DataFrame(X,columns=boston.feature_names)
cv = cross_val_score(regression, polyX, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation)
baseline = np.mean(cv)
improvements = [baseline]
for var_A in boston.feature_names:
polyX[var_A+’^2’] = polyX[var_A]**2
cv = cross_val_score(regression, polyX, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation)
improvements.append(np.mean(cv))
for var_B in boston.feature_names:
if var_A > var_B:
poly_var = var_A + ‘*’ + var_B
polyX[poly_var] = polyX[var_A] * polyX[var_B]
cv = cross_val_score(regression, polyX, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation)
improvements.append(np.mean(cv))

import matplotlib.pyplot as plt


plt.figure()
plt.plot(range(0,92),np.abs(improvements),’-’)
plt.xlabel(‘Caractéristiques polynomiales ajoutées’)
plt.ylabel(‘Erreur quadratique moyenne’)
plt.show()

Le suivi des améliorations au fur et à mesure de l’ajout de nouveaux termes complexes est
réalisé par le stockage de valeurs dans la liste nommée improvements. La visualisation
graphique des résultats est disponible dans la Figure 19.3. Nous constatons que certains ajouts
sont intéressants parce que l’erreur quadratique diminue alors que d’autres additions sont
inacceptables, puisqu’elles augmentent l’erreur.
Figure 19.3 : Augmentation de la puissance de prédiction par ajout de caractéristiques polynomiales.

Au lieu d’ajouter systématiquement toutes les variables générées, vous pourriez réaliser des
tests avant de choisir d’ajouter un terme quadratique ou une interaction. Il suffit de faire une
validation croisée pour vérifier si chaque addition apporte vraiment quelque chose à la
puissance de prédiction. Notre exemple constitue une bonne base de départ pour envisager
d’autres moyens de contrôler la complexité actuelle des jeux de données ou celle que vous avez
générée par transformation et création de caractéristiques au cours de votre phase
d’exploration des données. Avant d’aller plus loin, il est bon de vérifier le profil du jeu de
données actuel, ainsi que l’erreur quadratique moyenne de la validation croisée.

print(‘Nouveau profil de X:’, np.shape(polyX))


crossvalidation = KFold(n_splits=10, shuffle=True,
random_state=1)
cv = cross_val_score(regression, polyX, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation)
print(‘Erreur quadratique moyenne (EQM): %.3f’ % abs(np.mean(cv)))

L’erreur quadratique moyenne est bonne, mais le ratio entre 506 observations
et 104 caractéristiques n’est pas excellent parce que le nombre d’observations risque de ne
pas suffire pour bien estimer les coefficients :

Nouveau profil de X: (506, 104)


Erreur quadratique moyenne (EQM): 12.514

Donnez-vous comme règle de diviser le nombre d’observations par le nombre de coefficients.


Vous devriez disposer d’au moins 10 à 20 observations pour chaque coefficient à estimer dans
un modèle linéaire. L’expérience montre qu’il est même préférable d’en avoir au moins 30.

Régularisation d’un modèle linéaire


Les modèles linéaires sont connus pour leur biais élevé, mais au fur et à mesure de votre ajout
de caractéristiques d’interaction ainsi que de transformation, vous leur permettez de s’adapter
de mieux en mieux aux caractéristiques des données et de mieux savoir gérer le bruit, ce qui
leur permet d’augmenter la variance de leurs estimations. Subir une élévation de la variance
pour avoir moins de biais n’est pas toujours le meilleur choix, mais parfois, c’est le seul moyen
d’améliorer le pouvoir prédictif des algorithmes linéaires.
Pour contrôler l’arbitrage entre biais et variance, nous allons utiliser les deux types de
régularisation nommés L1 et L2. Cela permettra à l’algorithme de mieux pouvoir généraliser le
modèle. Lorsque vous ajoutez une de ces régularisations, cela provoque la mise en activité
d’une fonction dont la nature dépend de la complexité du modèle linéaire. Cette fonction va
pénaliser la fonction d’optimisation de coups. Dans les régressions linéaires, la fonction de
coût correspond à l’erreur au carré des prédictions ; pour la pénaliser, on utilise une somme
des coefficients des variables prédicteurs.
Dans un vrai modèle complexe, si le gain en prédiction est faible, la pénalisation va forcer la
procédure d’optimisation à supprimer toutes les variables inutiles ou au moins réduire leur
impact sur l’estimation. Cette régulation s’applique également aux caractéristiques fortement
corrélées, en limitant ou en supprimant leur contribution. Le résultat est une stabilisation de
résultats et donc une diminution de la variance des estimations :
Voyons les deux techniques disponibles :
» L1 (Lasso) : ramène certains coefficients à zéro, ce qui produit un champ de coefficients
peu dense. Réalise donc une sélection de variables.
» L2 (régression Ridge ou crête) : limite les coefficients des caractéristiques les plus
gênantes, mais les force rarement à zéro. Tous les coefficients continuent à servir à
l’estimation, mais nombreux d’entre eux deviennent si petits qu’ils n’ont plus d’influence.

Un hyperparamètre vous permet de contrôler la puissance de la régularisation, en général


c’est un coefficient que l’on appelle alpha. Lorsque cet alpha approche de 1.0, la
régularisation est forte et réduit donc beaucoup les coefficients. Il arrive ainsi qu’il tombe à
zéro. Ne confondez surtout pas la valeur de alpha avec C qui est le paramètre utilisé par la
régression logistique et par les machines à vecteurs de support. C correspond à 1/alpha, et
donc peut être supérieur à 1. Une faible valeur de C implique plus de régularisation, ce qui est
exactement le contraire de alpha.
La régularisation fonctionne par addition des coefficients des variables prédicteurs. Il est donc
indispensable que ces variables soient exprimées à la même échelle. Si ce n’est pas le cas, la
régularisation aura du mal à converger et les variables possédant un coefficient à grande
valeur absolue vont avoir un impact surdimensionné, donnant une régularisation que l’on
nomme infectieuse. Il est donc conseillé de toujours standardiser les valeurs des prédicteurs ou
de les associer à une plage bornée, par exemple [-1,+1]. Découvrons plusieurs techniques
d’utilisation des régularisations dans les deux modes L1 et L2, afin d’obtenir des effets variés.

La régression Ridge ou de crête (L2)


Commençons par la régularisation de type L2 qui réduit la force des coefficients sans les
annuler. La classe nommée Ridge incarne ce type L2. Elle est simple à utiliser puisqu’il n’y a
que le paramètre alpha à régler. La classe Ridge possède le paramètre normalize qui va
automatiquement normaliser les prédicteurs d’entrée pour une moyenne et une variance
unitaire zéro.
Après avoir trouvé le paramètre alpha optimal, nous obtenons le meilleur modèle suivant :

from sklearn.model_selection import GridSearchCV


from sklearn.linear_model import Ridge
ridge = Ridge(normalize=True)
search_grid = {‘alpha’:np.logspace(-5,2,8)}
search = GridSearchCV(estimator=ridge,
param_grid=search_grid,
scoring=’neg_mean_squared_error’,
refit=True, cv=10)
search.fit(polyX,y)
print(‘Meilleurs paramètres: %s’ % search.best_params_)
score = abs(search.best_score_)
print(‘CV EQM (MSE) des meilleurs paramètres: %.3f’ % score)

La plage dans laquelle il est conseillé de chercher la bonne valeur de alpha correspond à
np.logspace(-5,2,8). Bien sûr, si la valeur optimale que vous trouvez se trouve reléguée à
l’une des extrémités de la plage de test, il y a lieu d’agrandir la plage et de relancer le test.

Nous utilisons dans l’exemple de ce chapitre et le suivant les deux variables nommées polyX
et y. Elles ont été créées dans l’exemple de la création d’interaction entre les variables plus
haut dans ce chapitre. Si vous n’avez pas pratiqué cette section antérieure, vous ne pourrez
pas réussir les exemples de la présente section.

Régression Lasso (L1)


Découvrons maintenant le second type de régularisation, qui correspond à la classe nommée
Lasso. Elle se remarque par sa capacité à forcer à zéro les coefficients qui lui semblent les
moins utiles. Il en résulte une diminution de la densité des coefficients, puisque seuls certains
vont conserver une valeur supérieure non nulle. La classe exploite les mêmes paramètres que
la classe Ridge, comme déjà vu dans la section précédente.

from sklearn.linear_model import Lasso


lasso = Lasso(normalize=True,tol=0.05, selection=’random’)
search_grid = {‘alpha’:np.logspace(-5,3,8)}
search = GridSearchCV(estimator=lasso,
param_grid=search_grid,
scoring=’neg_mean_squared_error’,
refit=True, cv=10)
search.fit(polyX,y)
print(‘Meilleurs paramètres: %s’ % search.best_params_)
score = abs(search.best_score_)
print(‘CV MSE des meilleurs paramètres: %.3f’ % score)

Pour préparer la régression Lasso, nous choisissons un algorithme moins sensible (tol=0.05)
et une approche d’optimisation aléatoire (selection=‘random’). Nous obtenons une valeur
d’erreurs carrées moyennes supérieure à celle obtenue avec la régularisation de type L2 :

Meilleurs paramètres: {‘alpha’: 1e-05}


CV MSE des meilleurs paramètres: 12.432

Application de la régularisation
Vous pouvez profiter d’une procédure de sélection des caractéristiques à partir des coefficients
peu denses issus d’une régression L1. Vous pouvez donc réellement vous servir de la classe
Lasso pour sélectionner les variables les plus importantes. Vous réglez la quantité de variables
avec le paramètre alpha. Dans l’exemple, nous choisissons une valeur de 0.01 pour alpha, ce
qui permet d’obtenir une solution très simplifiée :

lasso = Lasso(normalize=True, alpha=0.01)


lasso.fit(polyX,y)
print(polyX.columns[np.abs(lasso.coef_)>0.0001].values)

Cette solution simplifiée ne montre que quelques interactions :

[‘CRIM*CHAS’ ‘ZN*CRIM’ ‘ZN*CHAS’ ‘INDUS*DIS’ ‘CHAS*B’


‘NOX^2’ ‘NOX*DIS’ ‘RM^2’ ‘RM*CRIM’ ‘RM*NOX’ ‘RM*PTRATIO’
‘RM*B’ ‘RM*LSTAT’ ‘RAD*B’ ‘TAX*DIS’ ‘PTRATIO*NOX’
‘LSTAT^2’]

Cette sélection de variables par L1 peut être appliquée automatiquement à une régression
comme à une classification au moyen des deux classes Randomized-Lasso et
RandomizedLogisticRegression. Dans les deux cas, le résultat est une série de modèles
régularisés et aléatoires L1. Le code conserve une trace des coefficients produits. Il s’agit de
tous les coefficients qui n’ont pas été forcés à zéro. Vous pouvez entraîner les deux classes au
moyen de la méthode fit(), mais il n’y a pas de méthode predict(). Elles disposent en
revanche d’une méthode transform() qui permet de réduire le jeu de données comme la
plupart des classes du module sklearn.preprocessing.

Combinaison de L1 et de L2 : ElasticNet
La régularisation de type L2 réduit l’impact des caractéristiques corrélées. La régularisation
de type L1 a tendance à sélectionner des caractéristiques. Une bonne stratégie consiste à
combiner les deux types en utilisant une somme pondérée, ce qui correspond à la classe
ElasticNet. Vous contrôlez les effets de L1 et de L2 par le paramètre alpha déjà connu ; pour
contrôler la proportion d’effets de L1 sur le résultat global, vous disposez du paramètre
l1_ratio. S’il est à zéro, vous utilisez en fait une régression Ridge. S’il est à 1, vous avez une
régression Lasso.

from sklearn.linear_model import ElasticNet


elastic = ElasticNet(normalize=True, selection=’random’)
search_grid = {‘alpha’:np.logspace(-4,3,8),
‘l1_ratio’: [0.10 ,0.25, 0.5, 0.75]}
search = GridSearchCV(estimator=elastic,
param_grid=search_grid,
scoring=’neg_mean_squared_error’,
refit=True, cv=10)
search.fit(polyX,y)
print(‘Meilleurs paramètres: %s’ % search.best_params_)
score = abs(search.best_score_)
print(‘CV MSE des meilleurs paramètres: %.3f’ % score)
Après un peu de patience, vous obtenez un résultat assez proche de celui de L1 seul :

Meilleurs paramètres: {‘alpha’: 0.0001, ‘l1_ratio’: 0.75}


CV MSE des meilleurs paramètres: 12.581

Les mégadonnées bouchée par bouchée


Ce livre n’a jusqu’ici travaillé qu’avec des bases de données de taille assez réduite. Dans la
réalité, les volumes de données peuvent être énormes ; il devient rapidement impossible de
tout charger en mémoire à la fois.
Les exemples qui vont suivre utilisent les deux variables polyX et y. Elles ont été définies dans
un exemple antérieur de ce chapitre, celui parlant des interactions avec les variables. Vous
devez exécuter les programmes de cette section antérieure pour que les exemples de celle-ci
puissent fonctionner.

Quand les données débordent


Dans un projet de datalogie, on peut considérer que le volume de données en entrée est
devenu énorme dans l’une des deux situations suivantes :
» Les données ne peuvent plus être toutes chargées dans la mémoire de la machine.
» Même si le système offre assez d’espace mémoire, l’application ne peut plus traiter les
données avec les algorithmes dans un temps raisonnable.

Descente de gradient stochastique (DGS)


Dès que vous avez trop de données, vous pouvez choisir comme prédicteur linéaire un
régresseur ou classifieur à descente de gradient stochastique. Le régresseur correspond à la
classe SGDRegressor et le classifieur à la classe SGDClassifier. La seule différence avec les
méthodes précédentes du chapitre est que les coefficients sont dorénavant optimisés une
observation à la fois. La conséquence est qu’il faut réaliser plus d’itérations avant d’arriver au
même résultat qu’avec une régression ridge ou lasso, mais la quantité de mémoire et le temps
nécessaire sont bien moindres.
Les deux prédicteurs se fondent sur une optimisation à descente de gradient stochastique (N.
d. T. : Nous abrégerons en SGD, Stochastic Gradient Descent). Dans cette approche,
l’ajustement du paramètre se produit après injection de chaque observation, et il en résulte un
parcours plus long et un peu plus erratique vers la réduction de fonction d’erreur. En
optimisant le travail sur des observations individuelles et non d’énormes matrices de données,
le temps d’entraînement de l’algorithme et la quantité de mémoire requise diminuent
énormément.
Lorsque vous adoptez une optimisation SGD, vous devez tester bien sûr différentes fonctions
de coût pour vérifier les performances. Vous pouvez également essayer les régularisations L1,
L2 et ElasticNet en ajustant le paramètre penalty ainsi que les deux paramètres de contrôle
correspondants alpha et l1_ratio. Certaines des variantes SGD résistent mieux que d’autres
aux aberrants, et notamment modified_huber pour la classification et huber pour la
régression.
L’approche SGD est sensible à l’échelle des variables, non seulement à cause de la
régularisation, mais aussi de par son fonctionnement interne. Vous devez donc toujours
standardiser vos caractéristiques par exemple en utilisant Standard-Scaler ou les forcer à
tenir dans la plage [0,+1] ou [-1,+1]. Si vous négligez cela, les résultats seront peu
intéressants.
Vous adoptez normalement une optimisation SGD parce que vous avez besoin de charger des
portions des données d’entrée en mémoire. Pour que l’entraînement soit efficace, vous devez
effectuer une standardisation en demandant à StandardScaler de produire la moyenne et
l’écart type du premier bloc de données. Les deux valeurs sont sans doute différentes pour la
totalité du jeu, mais vous pouvez faire démarrer une procédure d’apprentissage fonctionnelle
en réalisant une transformation avec ces estimations initiales.

from sklearn.linear_model import SGDRegressor


from sklearn.preprocessing import StandardScaler

SGD = SGDRegressor(loss=’squared_loss’,
penalty=’l2’,
alpha=0.0001,
l1_ratio=0.15,
max_iter=2000,
random_state=1)
scaling = StandardScaler()
scaling.fit(polyX)
scaled_X = scaling.transform(polyX)
cv = cross_val_score(SGD, scaled_X, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation)
score = abs(np.mean(cv))
print(‘CV MSE: %.3f’ % score)

L’erreur carrée moyenne après avoir exécuté SGDRegressor est la suivante :

CV MSE: 12.179

N. d. T. : Rappelons que MSE signifie Mean Squared Error ou erreur carrée moyenne.

Dans ce premier exemple, nous avons utilisé la méthode fit(), ce qui suppose que vous
pouvez charger toutes les données d’entraînement en mémoire. Pour entraîner le modèle en
plusieurs étapes, vous utilisez à la place la méthode par-tial_fit(). Elle ne lance qu’une
itération sur les données que vous fournissez puis les conserve en mémoire pour les ajuster en
recevant de nouvelles données. Le code demande dans ce cas un plus grand nombre
d’itérations :

from sklearn.metrics import mean_squared_error


from sklearn.model_selection import train_test_split

X_tr, X_t, y_tr, y_t = train_test_split(scaled_X, y,


test_size=0.20,
random_state=2)
SGD = SGDRegressor(loss=’squared_loss’,
penalty=’l2’,
alpha=0.0001,
l1_ratio=0.15,
max_iter=2000,
random_state=1)
improvements = list()
for z in range(10000):
SGD.partial_fit(X_tr, y_tr)
score = mean_squared_error(y_t, SGD.predict(X_t))
improvements.append(score)

La trace des améliorations partielles de l’algorithme ayant été conservée pour les
10 000 itérations sur les mêmes données, nous pouvons produire un diagramme pour voir
l’effet de ces améliorations, comme le montre le code suivant. Notez que vous auriez pu utiliser
des données différentes pour chaque étape.

import matplotlib.pyplot as plt


plt.figure(figsize=(8, 4))
plt.subplot(1,2,1)
range_1 = range(1,101,10)
score_1 = np.abs(improvements[:100:10])
plt.plot(range_1, score_1,’o--’)
plt.xlabel(‘Itérations jusque 100’)
plt.ylabel(‘Erreur de moindres carrés de test’)
plt.subplot(1,2,2)
range_2 = range(100,10000,500)
score_2 = np.abs(improvements[100:10000:500])
plt.plot(range_2, score_2,’o--’)
plt.xlabel(‘Itérations de 101 à 5000’)
plt.show()
Dans le diagramme de gauche de la Figure 19.4, nous constatons que l’algorithme commence
avec un taux d’erreur élevé, mais parvient à le réduire en quelques itérations, de 5 à 10. Le
taux continue ensuite à décroître doucement à chaque itération. Le diagramme de droite
montre qu’au bout de 1 500 itérations, le taux atteint un plancher puis recommence à croître.
C’est à partir de ce moment que vous subissez un sur-ajustement parce que les données sont
déjà entièrement consommées pour construire les règles ; vous forcez l’algorithme SGD à en
apprendre encore plus alors qu’il n’y a plus rien d’autre dans les données que du bruit.
L’algorithme va donc apprendre du bruit et des règles absurdes.
Puisque toutes les données ne tiennent pas en mémoire, il sera difficile d’effectuer une
recherche systématique et une validation croisée pour trouver la meilleure quantité
d’itérations. Une astuce consiste à garder un bloc de données d’entraînement pour réaliser la
validation en les stockant à part en mémoire ou sur disque. Vous pourrez ainsi vérifier vos
performances sur cette partie non connue et juger à partir de quand les performances
d’apprentissage de SGD commencent à décroître. Vous pourrez alors interrompre les itérations
à ce niveau, méthode appelée arrêt précoce.

Figure 19.4 : Résultat d’une optimisation de l’erreur carrée.

Machines à vecteurs de support (SVM)


Les datalogues considèrent les machines à vecteurs de support également appelées
Séparateurs à Vaste Marge (abrégé en SVM, Support Vector Machine) comme l’un des outils
de mécapprentissage les plus complexes et les plus puissants. Ce sujet n’est donc en général
abordé que dans les manuels pour utilisateurs avertis. Il n’y a pourtant aucune raison de se
passer de ces excellents algorithmes puisque la librairie Scikit-learn offre toute une série de
classes à supervision SVM très accessibles pour réaliser des régressions et classifications.
Vous disposez même d’une machine SVM non supervisée, celle qui a été utilisée dans le
Chapitre 16 à propos des aberrants. Voici les avantages à considérer lorsque vous envisagez
d’exploiter les algorithmes SVM comme solution d’apprentissage :
» une famille complète de techniques pour réaliser des classifications, des régressions
binaires et multiclasses et la détection des nouveautés ;
» un bon générateur de prédictions sachant bien gérer le sur-ajustement, le bruit et les
données aberrantes ;
» une bonne capacité à gérer les situations avec de nombreuses variables ;
» une efficacité maintenue lorsqu’il y a plus de variables que d’exemples ;
» de bonnes performances, même en allant jusqu’à 10 000 exemples d’entraînement ;
» une détection automatique des non-linéarités dans les données, ce qui évite de réaliser
des transformations complexes sur les variables.

Un beau programme, n’est-il pas ? Il faut également prendre en compte les quelques
inconvénients avant de se jeter sur l’importation du premier module SVM venu :
» SVM donne le meilleur de lui-même avec une classification binaire, qui était l’objectif
initial de cette technique. SVM n’est donc pas aussi bon avec les autres problèmes de
prédictions.
» SVM est moins efficace lorsqu’il y a plus de variables que d’exemples ; il faut dans ce cas
chercher une autre solution, par exemple SGD.
» SVM ne fournit qu’un résultat prédit ; pour obtenir des estimations avec probabilités pour
chaque réponse, il faut prévoir des calculs coûteux en temps.
» Le fonctionnement est correct dès le départ, mais il faut passer du temps à faire des
essais pour bien régler les nombreux paramètres et obtenir les meilleurs résultats.

De vraies méthodes mathématiques


Les machines SVM ont été inventées dans les 1990 par Vladimir Vapnik et ses collègues dans
les laboratoires AT&T. SVM a rencontré un grand succès grâce à ses excellentes performances
dans de nombreux problèmes coriaces qui se posaient à l’époque à la communauté de
l’apprentissage machine, en particulier la reconnaissance d’écriture manuscrite par un
ordinateur. De nos jours, SVM est adopté dans de très nombreux secteurs, des diagnostics
médicaux à la reconnaissance d’images et à la classification de textes. Vous trouverez
certainement vous-même une utilisation à SVM dans vos projets !
Le code de cette section étant assez long et complexe, nous avons décidé de le rendre
indépendant des autres exemples du chapitre en lui attribuant un fichier calepin distinct
portant le nom PYDASC_19_SVM.ipynb. Accédez à ce fichier pour voir et essayer les exemples et
visualiser les diagrammes de la section. Vous reviendrez ensuite au fichier calepin principal du
chapitre.
Le concept des machines à vecteurs de support SVM est simple, mais la mise en œuvre
mathématique est assez complexe car beaucoup de calculs sont nécessaires. Découvrons donc
la technologie concernée, car il est toujours utile de savoir comment fonctionne un outil pour
choisir quand et dans quel domaine il procurera les meilleurs résultats. Il faut d’abord
considérer la séparation en deux groupes de points de données, par exemple des étoiles et des
carrés en deux dimensions. C’est un problème de classification binaire habituel ; l’algorithme
doit apprendre à séparer une classe d’instances d’une autre uniquement à partir des
informations disponibles via les données. Le diagramme de gauche dans la Figure 19.5 montre
un tel problème.

Figure 19.5 : Séparation des données en deux groupes.

Lorsque les deux groupes se distinguent l’un de l’autre, vous résolvez le problème en
choisissant une ligne séparatrice à une position choisie. Il faut bien étudier les détails et
mesurer de façon précise. Au départ, l’opération de marquage de la séparation peut sembler
simple, mais il faut imaginer l’effet de l’arrivée de nouvelles données, plus tard. Vous ne
pourrez jamais être certain d’avoir choisi la bonne ligne séparatrice au départ.
Le diagramme de droite dans la Figure 19.5 montre deux solutions possibles, mais il y en a
d’autres. Ces deux solutions sont trop proches des observations (regardez la distance entre les
lignes et les premiers points de données). Rien ne permet de penser que les nouvelles
observations vont apparaître dans les mêmes régions que celles délimitées dans la figure. La
technique SVM limite le risque de mal choisir la ligne séparatrice (comme les lignes A et B de
la Figure 19.6). Elle cherche la solution en trouvant la distance la plus grande entre les points
frontières de chacun des groupes. Il est évident que lorsque vous cherchez l’espace maximal
entre les groupes, vous réduisez les chances de définir une ligne qui va devenir caduque en
tant que frontière !

Figure 19.6 : Une solution SVM valable pour la séparation entre deux groupes ou plus.

La plus grande distance entre deux groupes correspond à la marge. Si elle est suffisamment
grande, vous pouvez avoir de bonnes assurances que l’algorithme fonctionnera bien avec des
données actuellement inconnues. La marge est positionnée à partir des points présents en
limite de marge, points qui correspondent aux vecteurs de supports (la technique SVM tire son
nom de ces points).
Nous voyons une solution SVM dans le diagramme de gauche de la Figure 19.6. La marge est
une ligne tiretée, le séparateur une ligne continue et les vecteurs de support sont les points
cerclés.
Dans les problèmes réels, il n’est pas toujours simple de séparer les classes comme dans cet
exemple. Une machine SVM bien paramétrée peut résoudre certaines ambiguïtés
correspondant à des points mal classés. Un algorithme à SVM bien réglé peut faire des
miracles.
Pendant votre phase de prise en main de la machine SVC avec des exemples, il faut d’abord
chercher des solutions permettant aux points de données de bien expliquer le travail de
l’algorithme, afin de comprendre les concepts qui le régissent. Dans la réalité, vous devrez
vous contenter d’approximations acceptables ; vous observerez rarement des marges
importantes et univoques.
La technique SVM ne se limite pas aux classifications binaires en deux dimensions ; elle sait
traiter les données complexes, autrement dit, dès qu’il y a plus de dimensions ou dans des
situations telles que celles du deuxième diagramme de la Figure 19.6 : il est impossible ici de
séparer les groupes par une ligne droite.
Lorsqu’elle doit traiter de nombreuses variables, la machine SVM peut utiliser un plan de
séparation complexe que l’on appelle un hyperplan. SVM s’en sort également très bien lorsqu’il
n’est pas possible de séparer les classes par une ligne droite ou un plan ; en effet, cette
technique peut explorer des solutions non linéaires dans un espace à plusieurs dimensions
grâce à une technique de calcul correspondant à l’astuce de noyau (kernel trick).

Une foule de paramètres


La technique SVM est complexe, mais puissante. Après avoir trouvé la version de SVM la
mieux adaptée à votre projet, vous la faites travailler sur vos données pour optimiser certains
des nombreux paramètres qui vont améliorer les résultats. Voici les principales étapes de
configuration d’un modèle prédictif basé sur une machine SVM :
1. Sélection de la classe SVM à utiliser.
2. Entraînement du modèle avec les données.
3. Contrôle des erreurs de validation et utilisation en tant que témoin (baseline).
4. Expérimentation avec plusieurs valeurs des paramètres SVM.
5. Évaluation de l’amélioration apportée à l’erreur de validation.
6. Nouvel entraînement du modèle avec les paramètres optimisés.
Pour choisir la bonne classe SVM, vous devez qualifier votre problème. Vous choisirez par
exemple une classification (ou il faut deviner une classe) ou une régression (où il faut deviner
une valeur numérique). Dans le cas d’une classification, vous devez ensuite savoir s’il faut
classifier deux groupes (binaires) ou plus (classification multiclasse). Intéressez-vous ensuite à
la quantité de données à traiter. Dressez une liste de toutes vos exigences puis affinez vos
choix possibles en vous aidant du Tableau 19.1.

Tableau 19.1 : Module SVM des algorithmes d’apprentissage.

Classe Domaines d’utilisation Paramètres


clés
sklearn.svm.SVC Classification binaire et multiclasse lorsque la quantité C, kernel,
d’exemples est inférieure à 10 000 degree, gamma
sklearn.svm.NuSVC Similaire à SVC nu, kernel,
degree, gamma
sklearn.svm.LinearSVC Classification binaire et multiclasse lorsque la quantité Penalty, loss, C
d’exemples est supérieure à 10 000, données peu
denses
sklearn.svm.SVR Problèmes de régression C, kernel,
degree, gamma,
epsilon
sklearn.svm.NuSVR Similaire à SVR Nu, C, kernel,
degree, gamma
sklearn.svm.OneClassSVM Détection des données aberrantes nu, kernel,
degree, gamma

Il faut d’abord compter le nombre d’exemples dans les données. S’il y en a de l’ordre
de 10 000, les calculs vont être longs, mais cela n’empêche pas d’utiliser SVM pour des
problèmes de classification en optant pour la variante sklearn. svm.LinearSVC. Dans le cas
d’un problème de régression, vous remarquerez que LinearSVC n’est pas assez rapide ; dans
ce cas, vous vous tournerez vers une solution stochastique que nous décrivons dans une
section ultérieure.
Le module SVM de la librairie Scikit-learn englobe deux puissantes librairies écrites en
langage C : libsvm et liblinear. Un flux de données est échangé entre Python et les deux
librairies externes pendant l’ajustement du modèle. Les échanges de données sont lissés grâce
à un cache mémoire. Hélas, si le cache est trop petit pour le nombre de points de données à
gérer, il va constituer un goulet d’étranglement ! Si vous avez assez de mémoire, n’hésitez pas
à augmenter la taille de cache qui n’offre que 200 Mo en réglage usine. Vous pouvez aller si
possible jusqu’à 1000 Mo au moyen du paramètre de la classe SVM nommé cache_size. Si la
quantité d’exemples n’est pas importante, vous pouvez vous contenter de choisir entre
classification et régression.
Dans tous les cas, vous avez deux algorithmes. Pour une classification, vous pouvez choisir
entre sklearn.svm.SVC et sklearn.svm.NuSVC. La variante Nu ne se distingue que par ses
paramètres d’entrée et un algorithme légèrement différent. Vous obtenez quasiment les mêmes
résultats. En général, vous choisirez donc la version non-Nu.
Une fois l’algorithme exact choisi, vous allez vous intéresser aux paramètres, et notamment au
paramètre C qui stipule à quel point l’algorithme doit s’adapter aux points de données de
l’entraînement. Lorsque la valeur de C est faible, SVM s’adapte moins en cherchant à
emprunter une voie moyenne, car il n’utilise que quelques points et variables parmi ceux
disponibles. Avec une valeur importante pour C, le processus d’apprentissage est forcé de
travailler plus sur les points disponibles et prendre en compte de nombreuses variables.
En général, vous choisirez une valeur intermédiaire pour C après avoir fait quelques essais. Si
C est trop important, vous risquez un sur-ajustement, résultat d’une profonde adaptation du
SVM à vos données, qui provoquera une incapacité partielle à gérer les nouveaux problèmes.
Avec une valeur de C trop faible, vous aurez des prédictions imprécises, ce qui correspond à
un sous-ajustement : le modèle est trop simple pour le problème qu’il doit résoudre.
Après le paramètre C, vous vous intéresserez aux trois paramètres kernel, degree et gamma
qui sont interdépendants et dont la valeur dépend de la spécification de kernel. Par exemple,
un kernel valant linear n’a pas besoin des paramètres degree et gamma. La spécification du
paramètre kernel sert à décider si le modèle SVM va utiliser une ligne ou une courbe pour
deviner la mesure d’une classe ou d’un point. Puisque les modèles linéaires sont plus simples
et réussissent bien avec les nouvelles données, ils semblent intéressants, mais hélas, ils sont
moins efficaces dès que les variables données sont interconnectées de façon complexe. Vous
avez intérêt à commencer avec un noyau linéaire parce que vous ne savez pas d’avance si un
modèle linéaire va fonctionner avec votre projet. Vous réglez ensuite le paramètre C, puis vous
vous servez du modèle et des performances constatées que vous stockez dans un témoin pour
pouvoir tester ensuite des solutions non linéaires.

Classification avec SVC


Passons à la pratique et construisons notre premier modèle SVM. Dès le départ, les machines
SVM se sont montrées très efficaces pour classer des images manuscrites, et nous allons donc
commencer par un problème dans ce domaine. Cela vous donnera une idée de la puissance de
cette technique. Nous repartons du jeu de données d’exemple digits qui se trouve dans le
module datasets du paquetage Scikit-learn. Rappelons qu’il s’agit d’une série d’images de
huit pixels sur huit correspondant à une écriture à la main des chiffres de 0 à 9.

from sklearn import datasets


digits = datasets.load_digits()
X, y = digits.data, digits.target

Après chargement des jeux de données, nous demandons l’importation des données avec
load.digits, ce qui permet d’en extraire les prédicteurs (digits. data) pour les stocker dans
X et les classes prédites (digits.target) dans y.
Pour jeter un œil sur le contenu du jeu de données, vous pouvez vous servir des deux fonctions
de matplotlib subplot(), qui crée un tableau de dessins sur deux lignes de cinq colonnes et
imshow(), qui crée les valeurs des pixels en niveaux sur une grille de 8 sur 8. Le code source
organise les informations dans digits. images sous forme d’une série de matrices, chacune
contenant les pixels d’un chiffre.

import matplotlib.pyplot as plt


%matplotlib inline

for k,img in enumerate(range(10)):


plt.subplot(2, 5, k+1)
plt.imshow(digits.images[img],
cmap=’binary’,
interpolation=’none’)
plt.show()

Le code va afficher les 10 chiffres échantillonnés à partir des données de l’exemple


(Figure 19.7).

Figure 19.7 : 10 chiffres en écriture manuscrite tirés du jeu de données digits.

En observant ces données, vous pouvez en déduire que la technique SVM va pouvoir deviner
chaque chiffre en associant des probabilités avec les valeurs des pixels individuels dans chaque
grille. Le chiffre 2 n’offre pas les mêmes pixels noirs que le chiffre 1, ni même les mêmes
groupes de pixels. Il est de règle en datalogie d’essayer plusieurs approches et algorithmes
avant d’aboutir à un résultat acceptable. Il n’est jamais inutile de se montrer imaginatif et
intuitif pour tenter de déterminer au plus tôt quelle sera l’approche la plus fructueuse. Ici, si
vous explorez X, vous constatez qu’il y a exactement 64 variables, chacune indiquant le niveau
de grille d’un pixel. Au total, vous avez un grand nombre d’exemples, exactement 1797.

print(X[0])

Cette instruction affiche un vecteur pour le premier exemple du jeu :

[ 0. 0. 5. 13. 9. 1. 0. 0. 0. 0. 13. 15. 10. 15.


5. 0. 0. 3. 15. 2. 0. 11. 8. 0. 0. 4. 12. 0.
0. 8. 8. 0. 0. 5. 8. 0. 0. 9. 8. 0. 0. 4.
11. 0. 1. 12. 7. 0. 0. 2. 14. 5. 10. 12. 0. 0.
0. 0. 6. 13. 10. 0. 0. 0.]

Si vous reformatez l’affichage du même vecteur en tant que matrice de huit lignes sur huit
colonnes, vous pouvez même apercevoir la silhouette du chiffre zéro :

print(X[0].reshape(8, 8))

Les valeurs à zéro correspondent aux blancs et les valeurs supérieures à des nuances de gris :

[[ 0. 0. 5. 13. 9. 1. 0. 0.]
[ 0. 0. 13. 15. 10. 15. 5. 0.]
[ 0. 3. 15. 2. 0. 11. 8. 0.]
[ 0. 4. 12. 0. 0. 8. 8. 0.]
[ 0. 5. 8. 0. 0. 9. 8. 0.]
[ 0. 4. 11. 0. 1. 12. 7. 0.]
[ 0. 2. 14. 5. 10. 12. 0. 0.]
[ 0. 0. 6. 13. 10. 0. 0. 0.]]

Arrivé en ce point, vous vous demandez peut-être quoi faire au niveau des labels ou étiquettes.
Le paquetage NumPy propose à cet effet une fonction nommée unique() qui permet de
compter ces labels :

np.unique(y, return_counts=True)

Le résultat associe le label de classe qui est le premier nombre à sa fréquence, qu’il est
intéressant d’observer dans la deuxième ligne :

(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
array([178, 182, 177, 183, 181, 182, 181, 179, 174, 180],
dtype=int64))

Quasiment tous les labels correspondent au même nombre d’exemples à peu près. Vous pouvez
en déduire que les classes sont équilibrées ; la machine SVM ne va pas être influencée à croire
qu’une classe est plus probable que les autres.
S’il y avait un nombre de cas vraiment différents pour une ou plusieurs classes, vous seriez
face à un problème de déséquilibre, qui vous obligerait à réaliser le genre d’évaluation
suivante :
» conservation de la place déséquilibrée et recherche de prédiction biaisée en faveur des
classes les plus fréquentes ;
» rééquilibrage des classes en utilisant des pondérations, autrement dit, en donnant plus
de poids à certaines observations ;
» sélection pour éliminer certains cas dans les classes qui sont surpeuplées.

Vous résolvez un problème de classes déséquilibrées en réglant quelques autres paramètres.


Vous trouverez dans sklearn.svm.SVC le paramètre class_weight et le paramètre
sample_weight, au niveau de la méthode fit(). La façon la plus efficace et la plus rapide de
résoudre le problème consiste à indiquer le paramétrage class_weight=‘auto’ au moment de
la définition de la machine SVC, en laissant l’algorithme réaliser les autres réglages.
Vous devriez maintenant être prêt à tester SVC avec un noyau linéaire. N’oubliez pas d’abord
de répartir vos données en un jeu d’entraînement et un jeu de test pour pouvoir juger de
l’efficacité du modèle. Utilisez toujours une partie des données distinctes pour évaluer les
performances. Dans le cas contraire, les résultats sembleront très corrects au départ, mais
vont se détériorer au fur et à mesure de l’arrivée de nouvelles données.
from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import MinMaxScaler
X_tr, X_t, y_tr, y_t = train_test_split(X, y,
test_size=0.3,
random_state=0)

La fonction train_test_split() répartit X et y en deux en se servant de la valeur du


paramètre test_size qui vaut ici 0.3 et sert de référence pour la proportion du
partitionnement.

scaling = MinMaxScaler(feature_range=(-1, 1)).fit(X_tr)


X_tr = scaling.transform(X_tr)
X_t = scaling.transform(X_t)

Une fois les données réparties entre entraînement et test, il est conseillé de rééchelonner les
valeurs numériques, tout d’abord en récupérant les paramètres d’échelle auprès du jeu
d’entraînement puis en les appliquant via une transformation aux deux jeux d’entraînement et
de test.
Une dernière action de préparation à réaliser avant d’injecter les données dans la SVM est le
rééchelonnement qui va transformer toutes les valeurs pour qu’elles tiennent dans la plage
entre –1 et +1 (ou entre 0 et +1, si vous préférez). Cela vous évite de voir certaines variables
influencer l’algorithme de façon démesurée en lui faisant croire qu’elles sont réellement
prépondérantes simplement parce qu’elles ont de grandes valeurs. Les calculs seront ainsi
exacts, réguliers et rapides.
Le code source qui suit adapte les données d’entraînement à une classe SVC utilisant un noyau
linéaire. Nous effectuons une validation croisée puis testons les résultats au niveau de leur
précision (le pourcentage de chiffres correctement devinés) :

from sklearn.svm import SVC


svc = SVC(kernel=’linear’, class_weight=’balanced’)

Nous demandons à la machine SVC d’utiliser le noyau linear et de rééquilibrer les classes
automatiquement. Cette opération garantit qu’elles auront la même taille une fois que le jeu de
données aura été réparti en jeu d’entraînement et en jeu de test.

cv = cross_val_score(svc, X_tr, y_tr, cv=10)


test_score = svc.fit(X_tr, y_tr).score(X_t, y_t)

Nous déclarons ensuite deux variables. Les performances de la validation croisée vont être
mémorisées par la fonction cross_val_score() qui renvoie une liste de 10 scores après la
validation croisée à 10 plis (cv=10). Nous obtenons le résultat du test en appelant en séquence
deux méthodes sur l’algorithme d’apprentissage. Tout d’abord fit() pour ajuster le modèle,
puis score() pour évaluer le résultat sur le jeu de test en appliquant une précision moyenne,
c’est-à-dire un pourcentage moyen de résultats corrects parmi toutes les classes prédites.

print(‘Score de précision CV: %0.3f’ % np.mean(cv))


print(‘Score de précision Test: %0.3f’ % (test_score))

Il ne reste plus qu’à afficher les valeurs des deux variables et à évaluer le résultat. Il est assez
bon, puisque nous atteignons 97,4 % de prédictions correctes sur le jeu de test :

Score de précision CV: 0.983


Score de précision Test: 0.976

Si vous vous demandez ce qui se produirait en cas d’optimisation du paramètre principal C, au


lieu d’utiliser la valeur par défaut (1.0), vous trouverez réponse dans le script suivant. Nous
utilisons GridSearchCV pour chercher la valeur optimale de ce paramètre C :

from sklearn.model_selection import GridSearchCV


svc = SVC(class_weight=’balanced’, random_state=1)
search_space = {‘C’: np.logspace(-3, 3, 7)}

gridsearch = GridSearchCV(svc,
param_grid=search_space,
scoring=’accuracy’,
refit=True, cv=10)
gridsearch.fit(X_tr,y_tr)

La recherche GridSearchCV est un peu plus difficile à utiliser, mais elle permet de vérifier de
nombreux modèles en séquence. Il y a lieu de définir d’abord une variable pour l’espace de
recherche en utilisant un dictionnaire Python qui contiendra le planning d’exploration de la
procédure. Pour définir cet espace, vous créez le dictionnaire, ou s’il y en a plus d’un, une liste
de dictionnaire, pour chacun des groupes de paramètres à tester. Dans ce dictionnaire, les clés
correspondent aux noms des paramètres et les valeurs à une liste ou à une fonction qui génère
la liste contenant les valeurs que vous voulez tester :
La fonction de NumPy nommée logspace() génère une liste de sept valeurs pour C,
entre 10^–3 et 10^3. Le nombre de valeurs à tester devient considérable, mais le test est
exhaustif. En utilisant une telle plage, vous êtes certain que lorsque vous testez C et les autres
paramètres de SVM, vous avez testé toutes les possibilités.
Nous initialisons GridSearchCV en choisissant l’algorithme d’apprentissage, l’espace de
recherche, la fonction de notation et le nombre de plis de la validation croisée. Nous
demandons ensuite à la procédure, une fois la meilleure solution trouvée, d’ajuster la meilleure
combinaison de paramètres, afin que vous obteniez un modèle prédictif prêt à l’emploi :

cv = gridsearch.best_score_
test_score = gridsearch.score(X_t, y_t)
best_c = gridsearch.best_params_[‘C’]

Dorénavant, la variable gridsearch contient beaucoup d’informations concernant le meilleur


score, ainsi que les meilleurs paramètres, et même une analyse complète de toutes les
combinaisons évaluées. Elle contient en outre la méthode score() d’un emploi typique dans
les modèles prédictifs ajustés de Scikit-learn.

print(‘Score de précision CV: %0.3f’ % cv)


print(‘Score de précision Test: %0.3f’ % test_score)
print(‘Meilleur paramètre C: %0.1f’ % best_c)

Nous procédons à l’extraction des données de validation croisée et des scores de test puis nous
affichons la valeur de C correspondant au meilleur score :

Score de précision CV: 0.989


Score de précision Test: 0.987
Meilleur paramètre C: 10.0

La valeur proposée pour le paramètre C=100 permet effectivement d’améliorer les


performances de la validation croisée et du jeu de test.

Noyaux SVM non linéaires


Nous avons défini un modèle linéaire simple qui va servir de banc d’essai pour notre projet de
reconnaissance de chiffres manuscrits. Passons maintenant à une hypothèse plus complexe à
laquelle SVM répond avec tout un choix de noyaux non linéaires :
» polynomial (poly) ;
» fonction de base radiale ou Radial Basis Function (rbf) ;
» figmoïde (sigmoid) ;
» autres noyaux spécifiques avancés.

Le choix est vaste, mais vous utiliserez en général le noyau à fonction rbf. Il est plus rapide que
les autres et permet de trouver une approximation de quasiment toutes les fonctions non
linéaires.
Voici en quelques mots comment fonctionne un noyau rbf : il distribue les données en de très
nombreux groupes, ce qui lui permet plus facilement d’associer une réponse à chaque groupe.

Pour utiliser le noyau rbf, vous devez régler les paramètres degree et gamma en plus de C. Ils
sont faciles à paramétrer et une bonne recherche systématique trouvera toujours les valeurs
adéquates.
Le paramètre degree accepte une valeur à partir de deux. Elle sert à déterminer la complexité
de la fonction non linéaire qui va séparer les différents points. En pratique, vous pouvez
utiliser les valeurs 2, 3 ou 4 pour le paramètre degree dans une recherche systématique. Si
vous remarquez que votre meilleur résultat correspond à 4, vous pouvez tâter le terrain un peu
plus haut, en testant les valeurs 3, 4 et 5. Continuez à grimper, en sachant que vous irez
rarement plus haut que 5.
Le rôle du paramètre gamma dans notre algorithme est proche de celui de C en réalisant un
arbitrage entre sur- et sous-ajustement. Il n’existe que pour le noyau rbf. Une grande valeur
de gamma demande à l’algorithme de créer des fonctions non linéaires avec des profils
irréguliers, ce qui leur permet de mieux épouser les données. Une valeur faible génère des
fonctions plus régulières, quasiment sphériques, qui ignorent la plupart des irrégularités dans
les données.
Puisque vous connaissez maintenant les détails de cette approche non linéaire, nous pouvons
essayer rbf en conservant l’exemple précédent. Sachez dès maintenant que le traitement
risque d’être assez long (songez au grand nombre de combinaisons à tester). Tout dépend au
fond des performances de votre machine :

from sklearn.model_selection import GridSearchCV


svc = SVC(class_weight=’balanced’, random_state=1)
search_space = [{‘kernel’: [‘linear’],
‘C’: np.logspace(-3, 3, 7)},
{‘kernel’: [‘rbf’],
‘degree’:[2, 3, 4],
‘C’:np.logspace(-3, 3, 7),
‘gamma’: np.logspace(-3, 2, 6)}]
gridsearch = GridSearchCV(svc,
param_grid=search_space,
scoring=’accuracy’,
refit=True, cv=10,
n_jobs=-1)
gridsearch.fit(X_tr, y_tr)
cv = gridsearch.best_score_
test_score = gridsearch.score(X_t, y_t)
print(‘Score de précision CV: %0.3f’ % cv)
print(‘Score de précision Test: %0.3f’ % test_score)
print(‘Meilleurs paramètres: %s’ % gridsearch.best_params_)

La différence essentielle par rapport au script précédent est un espace de recherche plus
sophistiqué. En utilisant une liste, nous exploitons deux dictionnaires : un contenant les
paramètres à tester pour le noyau linéaire et un autre pour le noyau rbf. C’est ce qui nous
permet de comparer les performances des deux approches. Soyez patient pendant l’exécution
du code. Voici ce que vous allez enfin obtenir :

Score de précision CV: 0.990


Score de précision Test: 0.993
Meilleurs paramètres: {‘C’: 1.0, ‘degree’: 2, ‘gamma’: 0.1, ‘kernel’: ‘rbf’}

Les résultats montrent clairement que rbf est meilleur. La marge est cependant réduite par
rapport à un modèle linéaire, quand on pense au supplément de complexité et de temps de
traitement. Le meilleur modèle serait plus facile à déterminer en travaillant sur un plus grand
volume de données. Mais obtenir plus de données est coûteux en temps et en argent. Lorsque
vous n’avez pas vraiment de modèle qui se distingue, choisissez toujours le plus simple. Ici, le
noyau linéaire est évidemment plus simple que le noyau rbf.

Régression à vecteurs de support (SVR)


Nous n’avons pour l’instant géré que des classifications. La machine SVM existe également
dans une variante pour gérer les problèmes de régression. Puisque nous savons comment faire
une classification, il nous suffit d’apprendre que la classe correspondante porte le nom SVR et
qu’elle comporte un seul nouveau paramètre qui s’appelle epsilon. Toutes les autres
informations décrites pour la classification s’appliquent aux opérations de régression.
Pour cet exemple, nous utilisons un jeu de données de régression qui est celui des prix
immobiliers dans la région de Boston, maintenant bien connu. Rappelons qu’il provient de la
librairie StatLib maintenue à l’université de Carnegie Mellon. Il est utilisé dans de nombreux
projets d’apprentissage et de statistiques concernant la régression. Ce jeu comporte 506 cas
et 13 variables numériques dont l’une est binaire.

from sklearn.model_selection import train_test_split


from sklearn.model_selection import cross_val_score
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import MinMaxScaler
from sklearn.svm import SVR
from sklearn import datasets

boston = datasets.load_boston()
X,y = boston.data, boston.target
X_tr, X_t, y_tr, y_t = train_test_split(X, y,
test_size=0.3,
random_state=0)
scaling = MinMaxScaler(feature_range=(-1, 1)).fit(X_tr)
X_tr = scaling.transform(X_tr)
X_t = scaling.transform(X_t)

Notre objectif est d’estimer la valeur médiane des biens occupés. Nous allons essayer de
l’inférer au moyen d’une régression SVR (à vecteurs de support epsilon). Cette machine utilise
les paramètres C, kernel, degree et gamma déjà connus auxquels elle ajoute epsilon. Cette
mesure permet de connaître le taux d’erreur considéré comme acceptable par l’algorithme.
Une valeur forte pour epsilon implique un nombre de points de support faible alors qu’une
petite valeur pour epsilon nécessite un grand nombre de points de support. Autrement dit,
epsilon constitue un autre paramètre participant à l’arbitrage entre sur- et sous-ajustement.
L’expérience montre que pour l’espace de recherche de ce paramètre, nous pouvons nous
baser sur la séquence [0, 0.01, 0.1, 0.5, 1, 2, 4]. Vous commencez par la valeur 0 dans
laquelle l’algorithme n’accepte aucune erreur pour aller au maximum jusqu’à 4. Vous irez plus
loin dans l’espace de recherche seulement si vous constatez qu’une valeur d’epsilon élevée
apporte encore de meilleures performances.
Une fois l’espace de recherche de epsilon défini et SVR choisi comme algorithme, il ne reste
plus qu’à terminer le script. Notez qu’une fois de plus, les calculs peuvent prendre un certain
temps, car il y a beaucoup de combinaisons à évaluer.

svr = SVR()
search_space = [{‘kernel’: [‘linear’],
‘C’: np.logspace(-3, 2, 6),
‘epsilon’: [0, 0.01, 0.1, 0.5, 1, 2, 4]},
{‘kernel’: [‘rbf’],
‘degree’:[2,3],
‘C’:np.logspace(-3, 3, 7),
‘gamma’: np.logspace(-3, 2, 6),
‘epsilon’: [0, 0.01, 0.1, 0.5, 1, 2, 4]}]
gridsearch = GridSearchCV(svr,
param_grid=search_space,
refit=True,
scoring= ‘r2’,
cv=10, n_jobs=-1)
gridsearch.fit(X_tr, y_tr)
cv = gridsearch.best_score_
test_score = gridsearch.score(X_t, y_t)
print(‘Score R2 CV : %0.3f’ % cv)
print(‘Score R2 Test: %0.3f’ % test_score)
print(‘Meilleurs paramètres: %s’ % gridsearch.best_params_)

La recherche systématique (grid search) peut notamment prendre beaucoup de temps, même
si comme dans l’exemple nous utilisons toute la puissance disponible avec le paramètre
n_jobs=-1. Pour chaque noyau, le nombre de modèles à produire consiste à multiplier par les
combinaisons de valeurs des paramètres. Pour un noyau rbf, nous avons deux valeurs pour
degree, sept pour C, six pour gamma et sept pour epsilon, ce qui donne 588 modèles et chacun
doit être répliqué dix fois parce que cv=10. Il y a donc 5 880 modèles uniquement pour le
noyau rbf. Le code doit aussi tester le modèle linéaire, qui réclame 420 tests. Vous finissez
tout de même par obtenir les résultats :

Score R2 CV : 0.868
Score R2 Test: 0.834
Meilleurs paramètres: {‘C’: 1000.0, ‘degree’: 2, ‘epsilon’: 2, ‘gamma’: 0.1,
‘kernel’: ‘rbf’}

Précisons que l’erreur est calculée en utilisant R au carré, ce qui donne une mesure
entre 0 et 1 pour indiquer les performances du modèle, la valeur 1 étant la meilleure possible.

Solutions stochastiques avec SVM


Nous avons maintenant fait le tour de la famille des machines SVM et vous devriez pouvoir
convenir que ce sont de superbes outils pour les datalogues. Bien sûr, même les meilleures
solutions ont des soucis. Ces algorithmes peuvent par exemple être considérés comme ayant
trop de paramètres. C’est indéniable, surtout lorsque l’on songe au nombre de combinaisons
qu’il faut tester, et donc au temps nécessaire. Mais l’essentiel est le temps qu’il faut pour
entraîner la machine SVM. Vous avez remarqué que nos exemples utilisaient de petits jeux de
données avec peu de variables. Les recherches systématiques un peu ambitieuses prennent
bien plus de temps. Les jeux de données du monde réel sont encore plus volumineux.
L’entraînement et l’optimisation d’une machine SVM peuvent finir par prendre une éternité.
Lorsque vous avez trop de cas (une limite pratique correspond à 10000 exemples), une solution
éventuelle se trouve dans le même module SVM : la classe nommée LinearSVC. Elle ne
travaille qu’avec un noyau linéaire et se concentre sur la classification (pas de régression
possible) de grandes quantités d’exemples et variables à plus grande vitesse que la machine
SVC standard. De ce fait, LinearSVC constitue un excellent candidat pour les classifications de
type textuel. Cette classe demande de paramétrer moins de variables que la SVM normale (un
peu comme pour une classe de régression).
Découvrons ces paramètres :
» C : le paramètre de pénalisation. Une petite valeur suppose plus de régularisation (des
modèles plus simples avec des coefficients réduits ou forcés à zéro).
» loss : choix entre une valeur L1 (comme dans SVM) et L2 (les erreurs ont plus de poids et
l’algorithme tente plus d’ajuster les exemples mal classés).
» penalty : une valeur L2 (atténuation des paramètres moins importants) ou bien
L1 (forçage à zéro des paramètres non importants).
» dual : la valeur true ou false. Correspond au type de problème d’optimisation à résoudre.
N’a pas un grand impact sur le score obtenu mais en donnant la valeur false à ce
paramètre, les calculs sont plus rapides qu’avec la valeur true.

Les trois paramètres loss, penalty et dual sont liés par des contraintes réciproques.
Consultez le Tableau 19.2 pour décider quelle combinaison vous voulez utiliser.

Tableau 19.2 : Contraintes des paramètres loss, penalty et dual.

Penalty Loss Dual


l1 l2 False
l2 l1 True
l2 l2 True; False

Notez que l’algorithme n’accepte pas la combinaison des valeurs penalty=’l1’ et loss=’l1’.
En revanche, vous simulez parfaitement l’approche d’optimisation SVC avec la combinaison
penalty=’l2’ et loss=’l1’.

La machine LinearSVC est donc assez rapide, ce que prouve un test en comparant avec SVC :

from sklearn.datasets import make_classification


from sklearn.model_selection import train_test_split
import numpy as np
X,y = make_classification(n_samples=10**4,
n_features=15,
n_informative=10,
random_state=101)
X_tr, X_t, y_tr, y_t = train_test_split(X, y,
test_size=0.3,
random_state=1)

from sklearn.svm import SVC, LinearSVC


svc = SVC(kernel=’linear’, random_state=1)
linear = LinearSVC(loss=’hinge’, random_state=1)

svc.fit(X_tr, y_tr)
linear.fit(X_tr, y_tr)
svc_score = svc.score(X_t, y_t)
libsvc_score = linear.score(X_t, y_t)
print(‘Score de précision de test SVC: %0.3f’ % svc_score)
print(‘Précision de test LinearSVC: %0.3f’ % libsvc_score)

Les résultats qualitatifs sont proches de ceux de la machine SVC :

Score de précision de test SVC: 0.803


Précision de test LinearSVC: 0.804

Nous commençons par créer un jeu de données artificielles avec make_classi-fication()


puis nous évaluons à quel point les deux algorithmes arrivent à quasiment le même résultat.
Nous testons la vitesse d’exécution des deux solutions sur ce jeu synthétique pour estimer
quelle est leur capacité à monter en charge au niveau du volume de données :

import timeit
X,y = make_classification(n_samples=10**4,
n_features=15,
n_informative=10,
random_state=101)
t_svc = timeit.timeit(‘svc.fit(X, y)’,
‘from __main__ import svc, X, y’,
number=1)
t_libsvc = timeit.timeit(‘linear.fit(X, y)’,
‘from __main__ import linear, X, y’,
number=1)
print(‘Meilleur temps moyen (sec.) SVC: %0.1f’ % np.mean(t_svc))
print(‘Meilleur temps moyen (sec.) LinearSVC: %0.1f’ % np.mean(t_libsvc))

Le système servant d’exemple produit les résultats suivants, résultats pouvant légèrement
diverger sur votre équipement :

Meilleur temps moyen (sec.) SVC: 11.3


Meilleur temps moyen (sec.) LinearSVC: 0.2

Avec le même volume à traiter, LinearSVC est clairement plus rapide que SVC. Pour être
précis, 11.3 / 0.2 = 55 fois plus rapide. Voyons ce qui se passe lorsque l’on augmente le
volume, par exemple en le triplant :

Meilleur temps moyen (sec.) SVC: 104.9


Meilleur temps moyen (sec.) LinearSVC: 1.6

Nous constatons que le temps requis par SVC augmente plus vite (9,3 fois) que celui requis par
LinearSVC (8 fois). En effet, SVC va demander de plus en plus de temps, et les choses vont
empirer au fur et à mesure de l’augmentation de volume. Voici les résultats avec cinq fois plus
de données :

Meilleur temps moyen (sec.) SVC: 352.3


Meilleur temps moyen (sec.) LinearSVC: 2.6

L’algorithme SVC devient rapidement inutilisable. Vous choisirez donc LinearSVC dès que vous
avez de gros volumes à traiter. Mais lorsque vous avez des millions d’exemples à classifier ou à
régresser, il reste à vérifier que LinearSVC reste dans la course. Nous avons vu comment la
classe SGD, avec SGDClassifier et SGDRegressor, permettait d’utiliser un algorithme du type
SVM lorsqu’il y avait des millions de lignes de données, sans trop mettre à genoux la machine.
Pour SGDClassifier, il suffit de régler le paramètre loss à la valeur ‘hinge’. Pour
SGDRegressor, il faut régler le même paramètre à ‘epsilon_insensi-tive’ et paramétrer le
paramètre epsilon lui-même.
Réalisons un autre test de performances pour voir les avantages et inconvénients des deux
approches LinearSVC et SGDClassifier :

from sklearn.datasets import make_classification


from sklearn.model_selection import train_test_split
from sklearn.model_selection import cross_val_score
from sklearn.svm import LinearSVC
import timeit

from sklearn.linear_model import SGDClassifier


X, y = make_classification(n_samples=10**5,
n_features=15,
n_informative=10,
random_state=101)
X_tr, X_t, y_tr, y_t = train_test_split(X, y,
test_size=0.3,
random_state=1)

Nous avons environ 100 000 cas à traiter. Si votre machine dispose d’assez de mémoire et si
vous avez la patience, vous pouvez encore augmenter le nombre de cas de l’entraînement ou le
nombre de caractéristiques afin de tester les deux algorithmes de façon très intensive.

linear = LinearSVC(penalty=’l2’,
loss=’hinge’,
dual=True,
random_state=1)
linear.fit(X_tr, y_tr)
score = linear.score(X_t, y_t)
t = timeit.timeit(«linear.fit(X_tr, y_tr)»,
“from __main__ import linear, X_tr, y_tr”,
number=1)
print(‘Précision test LinearSVC: %0.3f’ % score)
print(‘Temps moyen LinearSVC: %0.1f secs’ % np.mean(t))

Sur notre machine de test, LinearSVC a fait son travail pour toutes les lignes en un peu plus
de quatre secondes :

Précision test LinearSVC: 0.796


Temps moyen LinearSVC: 4.3 secs

Voici le code pour tester SGDClassifier selon le même principe :

sgd = SGDClassifier(loss=’hinge’,
max_iter=100,
shuffle=True,
random_state=101)
sgd.fit(X_tr, y_tr)
score = sgd.score(X_t, y_t)
t = timeit.timeit(«sgd.fit(X_tr, y_tr)»,
“from __main__ import sgd, X_tr, y_tr”,
number=1)

print(‘Précision test SGDClassifier: %0.3f’ % score)


print(‘Temps moyen SGDClassifier: %0.1f secs’ % np.mean(t))

Nous constatons que SGDClassifier n’a eu besoin que d’environ une seconde pour traiter les
mêmes données et fournir le même score en sortie :

Précision test SGDClassifier: 0.796


Temps moyen SGDClassifier: 1.1 secs

En augmentant la valeur du paramètre n_iter, vous améliorez les performances, mais


également la longueur de traitement. Vous pouvez augmenter le nombre d’itérations, sans aller
au-delà d’une valeur que vous devez trouver par test. Si vous allez plus haut, les performances
vont commencer à chuter, en raison du sur-ajustement.
Incursion dans les réseaux neuronaux
En étudiant et en cherchant à comprendre le fonctionnement de la transmission des signaux
dans un cerveau, les chercheurs ont établi l’idée des réseaux neuronaux à partir d’analogies
biologiques et des organes utilisés ; ils ont réutilisé des noms tels que neurones et axones. En
réalité, vous allez vite découvrir qu’un réseau neuronal ressemble plutôt à un mécanisme de
régression linéaire sophistiquée : il additionne des coefficients qui sont multipliés par des
valeurs numériques d’entrée. La partie du processus dans laquelle ces sommes sont produites
correspond aux neurones.
Un réseau neuronal ne cherche pas à fonctionner selon les mêmes principes qu’un cerveau,
puisque c’est une machine arithmétique (N. d. T. : des courants électriques dans du sable
purifié). Les algorithmes correspondants sont pourtant incroyablement efficaces pour résoudre
des problèmes complexes tels que la reconnaissance d’images et de sons ou la traduction
automatique. Si le matériel est assez puissant, les prédictions sont réalisées rapidement. Un
réseau neuronal bien conçu est une machine d’apprentissage profond (deep learning). C’est le
genre d’outil qui anime Siri et autres assistants vocaux, et permet de réaliser des applications
de mécapprentissage époustouflantes.
Pour profiter d’un apprentissage profond, il faut un matériel adapté, possédant au moins un
coprocesseur graphique GPU ; la machine doit être dotée d’une infrastructure logicielle
spécifique dont voici quelques composants :
» Tensorflow (https://www.tensorflow.org/),
» MXNet (https://mxnet.apache.org/),
» Pytorch (https://pytorch.org/) ou
» Chainer (https://chainer.org/).

Une présentation approfondie des réseaux neuronaux n’entre pas dans le cadre de ce livre ;
cela dit, nous allons présenter une implémentation simple, disponible dans la librairie Scikit-
learn. Nous allons ainsi créer rapidement un réseau neuronal pour pouvoir le comparer à
d’autres algorithmes de mécapprentissage.

Principe d’un réseau neuronal


L’algorithme central d’un réseau neuronal correspond aux neurones ou unités. Normalement,
plusieurs neurones sont interconnectés en plusieurs couches, chaque neurone étant lié aux
entrées et aux sorties de plusieurs autres. Un neurone peut donc recevoir en entrée des
caractéristiques ou bien les résultats d’autres neurones, en fonction de sa position dans la
structure neuronale.
Les algorithmes que nous avons vus jusqu’ici sont tous caractérisés par un canal de données
qui détermine la façon dont les algorithmes reçoivent, traitent puis produisent les données.
Dans un réseau neuronal, c’est à vous de choisir comment l’information doit circuler : vous
choisissez le nombre d’unités ou neurones et leur distribution en couches. La mise en place
d’un réseau neuronal est donc autant un art qu’une science ; vous apprenez par l’expérience à
arranger les neurones en couches pour obtenir les meilleures prédictions. De façon plus
technique, chaque neurone reçoit en entrée des valeurs pondérées en grand nombre, il les
additionne et produit le résultat en sortie.
Un réseau neuronal ne peut traiter que des informations numériques et continues ; il ne peut
pas traiter des variables qualitatives telles que des étiquettes correspondant à des qualités
comme les couleurs rouge, vert ou bleu dans une image. Pour traiter les variables qualitatives,
vous devez d’abord les transformer en variables numériques continues, par exemple sous
forme de valeurs binaires.
Les neurones sont en mesure de faire plus qu’une simple sommation : une vraie
transformation. Les chercheurs ont remarqué en observant la nature que les vrais neurones
recevaient bien des signaux, mais que cela ne provoquait pas systématiquement le
déclenchement d’un signal de leur part. Tout dépend du niveau du signal d’entrée. Lorsque le
neurone est assez stimulé, il active sa sortie (il fait feu). Dans le cas contraire, il ne fait rien,
les neurones d’un réseau neuronal produisent la somme des valeurs pondérées qu’ils reçoivent,
puis font évaluer le résultat par une fonction d’activation qui réalise une transformation non
linéaire. Cette fonction peut ainsi générer une valeur à zéro tant que sa valeur d’entrée reste
sous un seuil. Elle peut également réduire ou amplifier une valeur de façon non linéaire et
produire un signal recalculé en sortie.
Chacun des neurones du réseau reçoit ses entrées de ceux des couches antérieures, seule la
première couche étant directement connectée aux données d’entrée. Le neurone effectue une
pondération, une addition et une transformation grâce à la fonction d’activation. La sortie de
l’activation devient l’entrée des autres neurones, ou bien la prédiction résultante pour la
dernière couche. Un réseau neuronal constitué d’un certain nombre de neurones et de couches
propose donc une structure efficace de prédiction en se basant sur les pondérations appliquées
par chacun des neurones pour ses entrées. Ces pondérations ne sont pas très différentes des
coefficients dans les régressions linéaires. Le réseau apprend progressivement leur valeur en
réalisant des passes répétées qui sont des itérations (des époques) sur les exemples trouvés
dans le jeu de données d’entrée.

Classification et régression neuronales


La librairie Scikit-learn propose deux classes et fonctions pour incarner un réseau neuronal :
» MLPClassifier : incarne un perceptron multicouche MLP (multilayer perceptron) pour
faire de la classification. Les sorties produites (une ou plusieurs en fonction du nombre de
classes à prédire) sont des probabilités que l’exemple fasse partie d’une certaine classe.
» MLPRegressor : incarne un perceptron multicouche pour les problèmes de régression.
Toutes les sorties (parce que la machine peut prédire plusieurs valeurs cibles à la fois) sont
des estimations des mesures à prédire.

Les deux fonctions ont exactement les mêmes paramètres. Nous pouvons donc n’étudier qu’un
exemple pour la classification. Nous repartons du jeu de données des chiffres manuscrits
auxquels nous allons appliquer une classification MLP. Nous commençons comme d’habitude
par importer les paquetages, charger le jeu de données en mémoire puis le répartir en un jeu
d’entraînement et un jeu de test (de la même façon que pour les machines à vecteurs de
support SVM) :

from sklearn.model_selection import train_test_split


from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import MinMaxScaler
from sklearn import datasets
from sklearn.neural_network import MLPClassifier

digits = datasets.load_digits()
X, y = digits.data, digits.target
X_tr, X_t, y_tr, y_t = train_test_split(X, y,
test_size=0.3,
random_state=0)

Il est indispensable de bien prétraiter les données en entrée du réseau neuronal car les
réseaux neuronaux sont sensibles aux problèmes d’échelle et à la distribution des données. Il
est donc conseillé de d’abord normaliser les données en forçant la moyenne à zéro et la
variance à un ou à redimensionner celle-ci en choisissant comme minimum et maximum les
valeurs –1 et +1 ou 0 et +1. Vous verrez quelle transformation fonctionne le mieux par
expérimentation. L’expérience acquise montre que le redimensionnement pour la plage –1 à
+1 semble la meilleure. C’est ce que nous réalisons dans notre exemple :

scaling = MinMaxScaler(feature_range=(-1, 1)).fit(X_tr)


X_tr = scaling.transform(X_tr)
X_t = scaling.transform(X_t)

Nous avons déjà dit qu’il était conseillé de définir la transformation du prétraitement
seulement sur les données d’entraînement, puis d’appliquer la procédure ainsi apprise aux
données de test. C’est le seul moyen de tester correctement à quel point le modèle réagira bien
à des données différentes.
La configuration de votre perceptron MLP requiert le réglage de quelques paramètres. Si vous
négligez d’y consacrer le soin approprié, vous serez déçu par les résultats. En effet, MLP n’est
pas un algorithme prêt à l’emploi dès le départ. Vous devez d’abord définir l’architecture des
neurones, choisir combien il en faut dans chaque couche et combien il faut de couches. Le
nombre de neurones dans chaque couche est défini avec le paramètre hidden_layer_sizes. Il
reste ensuite à choisir le bon solveur :
» L-BFGS : destiné aux petits jeux de données ;
» Adam : destiné aux grands jeux de données ;
» SGD (Descente de Gradient Stochastique, DGS) : résout la plupart des problèmes si
certains paramètres spéciaux sont bien configurés. Il s’agit notamment du taux
d’apprentissage qui correspond à la vitesse à laquelle le système apprend et au « moment
de Nesterov », valeurs qui aident le réseau à éviter d’utiliser les solutions les moins
intéressantes. Pour le taux d’apprentissage, vous devez choisir la valeur initiale
learning_rate_init qui est en général de 0.001 (mais peut encore être inférieure) et la
vitesse de changement pendant l’entraînement (paramètre learning_rate qui peut
correspondre à une des trois valeurs ‘constant’, ‘invscaling’ ou ‘adaptive’).

Le paramétrage fin d’un solveur SGD est assez complexe. Vous ne pouvez évaluer l’impact des
paramètres sur les données qu’en les testant dans le cadre d’une optimisation des
hyperparamètres. La plupart des gens préfèrent donc démarrer avec un solveur L-BFGS ou
Adam.
Un autre hyperparamètre critique correspond à max_iter qui règle le nombre d’itérations et
peut entraîner des résultats très différents si la valeur est trop élevée ou trop faible. Par
défaut, elle est égale à 200, mais il est toujours préférable d’augmenter ou de diminuer la
valeur après avoir fixé les autres paramètres. Vous devez enfin demander un rebattage ou
mélange des données avec shuffle=True et choisir un état aléatoire initial avec random_state
pour garantir que les résultats pourront être reproduits. Notre exemple choisit de travailler
avec 512 nœuds dans une seule couche, d’utiliser le solveur Adam et le nombre d’itérations
standard de 200 :

nn = MLPClassifier(hidden_layer_sizes=(512, ),
activation=’relu’,
solver=’adam’,
shuffle=True,
tol=1e-4,
random_state=1)

cv = cross_val_score(nn, X_tr, y_tr, cv=10)


test_score = nn.fit(X_tr, y_tr).score(X_t, y_t)
print(‘Score précision CV: %0.3f’ % np.mean(cv))
print(‘Score précision Test: %0.3f’ % (test_score))

Cet exemple réussit bien à classifier les chiffres manuscrits à partir d’un perceptron MLP, en
arborant des scores CV et de test corrects :

Score précision CV: 0.978


Score précision Test: 0.981

Ces résultats sont un peu meilleurs que ceux d’une machine SVC, mais cela suppose un
paramétrage fin de quelques valeurs. Ne vous attendez jamais à une approche prête à l’emploi
avec un algorithme non linéaire, sauf à adopter une des solutions basées sur un arbre de
décision, ce qui est justement le sujet du prochain chapitre
Chapitre 20
Plus forts à plusieurs
DANS CE CHAPITRE :
» Principe des arbres de décisions

» Forêts aléatoires et autres techniques d’ensachage (bagging)

» Avantages des meilleurs ensembles GBM

Ce chapitre est l’occasion d’aller plus loin que les modèles d’apprentissage individuels qui nous
ont occupés jusqu’ici. Nous allons découvrir la puissance des ensembles, des groupes de
modèles laissant présager des performances bien supérieures. Un ensemble apporte une
intelligence collective en partageant des informations pour générer de meilleures prédictions.
L’idée de base est qu’un groupe d’algorithmes peu performants parvient à produire de
meilleurs résultats qu’un seul modèle très bien entraîné.
Il vous est peut-être arrivé de participer à un jeu dans lequel il fallait deviner le nombre de
billes dans un vase. Chaque participant a peu de chances de trouver le bon nombre, mais des
expérimentations ont montré qu’en collectant un grand nombre de fausses réponses puis en
produisant leur moyenne, on parvient à s’approcher de la bonne réponse ! Cette sagesse des
foules s’explique par le fait que les mauvaises réponses tendent à se répartir autour de la
bonne. Voilà pourquoi vous parvenez presque à la bonne réponse en faisant la moyenne des
mauvaises réponses.
Lorsque vous devez faire des prédictions complexes dans un projet de datalogie, vous pouvez
tirer profit des compétences diverses de plusieurs algorithmes d’apprentissage pour obtenir
des prédictions plus précises qu’avec un seul. Nous allons dans ce chapitre mettre en place un
processus qui va permettre de tirer avantage de plusieurs algorithmes pour obtenir de
meilleures réponses.
Le fichier calepin du code source de ce chapitre correspond à PYDASC_20.

Arbres de décision classiques


Les arbres de décision sont utilisés depuis longtemps pour le minage de données, bien avant
les années 1970. Ils ont été adoptés dans de nombreux domaines d’activité, parce que
l’algorithme est intuitif, les résultats faciles à comprendre et les arbres sont efficaces par
rapport à un modèle linéaire simple. Depuis, des algorithmes plus puissants sont apparus et les
arbres de décision ont été un peu oubliés pendant un certain temps, accusés de trop aisément
provoquer un sur-ajustement. Ils sont revenus en faveur récemment en tant que briques pour
construire des algorithmes d’ensemble. De nos jours, les arbres tels que les forêts aléatoires ou
les machines GBM (Gradient Boosted Machine) sont devenus des éléments fondamentaux des
applications de datalogie modernes.

Principe d’un arbre de décision


Le concept d’arbre de décision se base sur l’idée que l’on peut subdiviser un jeu de données en
segments de moins en moins grands tout en appliquant des règles en fonction des valeurs que
possèdent les caractéristiques des données. Cette répartition suppose que l’algorithme
choisisse de bonnes coupes pour optimiser les chances de trouver les résultats cherchés
correctement, soit en tant que classes, soit en tant qu’estimations. L’algorithme doit donc faire
en sorte que certaines classes ou certaines moyennes de valeurs soient présentes dans
chacune des portions.
Pour illustrer le fonctionnement d’un arbre de décision, nous vous proposons de prédire les
chances de survie d’un passager du paquebot Titanic qui avait coulé dans l’Atlantique Nord en
avril 1912, après une collision que vous connaissez. Plusieurs jeux de données sont disponibles
sur le Web pour documenter cette tragédie. Citons notamment le site Encyclopedia Titanica
(https://www.encyclopedia-titanica.org) qui regorge d’articles, de biographies et de
données. Le concours de datalogie Kaggle a également fait participer des dizaines de milliers
de personnes sur ce thème (https://www.kaggle.com/c/titanic).
Les jeux de données concernant le Titanic varient beaucoup au niveau du contenu. Nous allons
choisir dans ce chapitre celui qui est disponible librement au département de biostatistiques
de l’École de médecine de l’université Vanderbilt. Vous pouvez le télécharger à l’adresse
http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic3.csv. Il
contient 1 309 enregistrements de passagers avec toutes leurs statistiques.
Aucun membre d’équipage n’est présent dans les données parce que ce jeu se concentre sur
les passagers commerciaux, cherchant à savoir si le fait d’avoir survécu est dû à la chance ou à
la cabine qu’occupaient les passagers au moment de la collision. Globalement, le taux de survie
a été de 38,2 % (809 passagers sur 1 309 sont décédés). Une étude des caractéristiques des
passagers permet à l’arbre de décision de déterminer les points suivants :
» Le fait d’être de sexe masculin fait baisser les chances de survie de 38,2 % à 19,1 %.
» Le fait d’être de sexe masculin, mais d’avoir moins de 9,5 ans fait remonter les chances
de survie à 58,1 %.
» Le fait d’être de sexe féminin, quel que soit l’âge, offre une probabilité de survie
de 72,7 %.

Ces statistiques permettent de construire facilement un arbre tel que celui qui est illustré dans
la Figure 20.1. Pour obtenir ce genre de graphique ainsi que celui du jeu de données Iris un
peu plus loin dans ce chapitre, nous avons profité du paquetage nommé dtreeviz créé par le
professeur Terence Parr et Prince Grover de l’université de San Francisco
(https://parrt.cs.usfca.edu). Si vous avez besoin de créer des visualisations à partir de vos
arbres de décision, récupérez le paquetage et les conseils d’installation à l’adresse
https://github.com/parrt/dtreeviz. Des explications concernant la création et l’utilisation
du paquetage sont disponibles sur le blog du professeur Parr
(https://explained.ai/decision-tree-viz).

Figure 20.1 : Arbre de décision du taux de survie sur le Titanic.

N.d.T. : Si vous utilisez la version française du fichier titanic.csv, la mention survived


correspond au nom de la colonne statut, la mention perished à DCD et survived à
survivant.

Dans la figure, l’arbre est inversé avec la racine en haut et les branches qui en descendent.
Tout en haut est représenté le jeu de données complet. La première fourche utilise le sexe
comme critère pour créer deux branches dont une donne directement sur une feuille (un
segment terminal). Les cas dans les feuilles sont classifiés selon la classe la plus fréquente, ou
par un calcul de la probabilité élémentaire des cas ayant les mêmes caractéristiques. Cela
donne la probabilité dans la feuille. L’autre branche est subdivisée par âge.
Procédons à une lecture des nœuds de l’arbre. Le premier nœud rend compte de la règle qui
régit la première fourche et mène à tous les autres nœuds. L’arbre de la Figure 20.1 considère
que le sexe est le meilleur prédicteur. Dans le nœud du haut, la variable is_female est
représentée sous forme de barres verticales. La barre de gauche correspond au sexe masculin
et celle de droite au sexe féminin. Vous constatez à première vue que les femmes ont un taux
de survie supérieur (la zone grisée dans la version imprimée ou vert clair sur écran).
Au deuxième niveau, les hommes sont séparés des femmes. Du côté droit du deuxième niveau,
nous trouvons un nœud qui n’est peuplé que par des femmes avec un histogramme qui montre
que quasiment toutes les femmes de première et de deuxième classe ont survécu et qu’environ
la moitié de celles de la troisième classe ont péri. Nous pouvons en déduire une première
règle : les femmes des première et deuxième classes peuvent être classifiées comme
survivantes parce que la probabilité est forte pour elles. Ce n’est pas le cas pour celles de la
troisième classe. Pour en savoir plus à ce niveau, il faudrait créer un nouvel embranchement
pour extraire d’autres détails.
Du côté des hommes, le deuxième niveau montre que c’est l’âge qui est le critère parce que
quasiment tous ceux de sexe masculin ayant moins de 10 ans environ ont survécu, alors que
ceux plus âgés ont presque tous péri. L’arbre s’arrête à ce niveau, mais nous pourrions
exploiter d’autres critères pour connaître plus en détail les règles de partitionnement et la
probabilité de survie en fonction d’autres caractéristiques. En descendant dans l’arbre, nous
confirmons que la plupart des survivants étaient des femmes avec leurs enfants, ce qui
coïncide avec la règle en cas de naufrage « Les femmes et les enfants d’abord ! » . Cette
répartition correspond bien à la situation du Titanic dans laquelle il n’y avait pas assez de
canots de sauvetage (parce que l’armateur croyait son navire insubmersible). Vous en saurez
plus sur ce drame lié aux canots de sauvetage insuffisants en cherchant sur le Web
(https://www.historyonthenet.com/the-titanic-lifeboats/).
Dans cet arbre, chaque fourche est binaire, mais il est possible d’utiliser des fourches à
plusieurs branches, si l’algorithme le permet. Dans le paquetage Scikit-learn, les deux classes
DecisionTreeClassifier et DecisionTreeRegressor dans le module sklearn.tree sont
toutes deux des arbres binaires. Voici dans quelles conditions un arbre de décision arrête de
segmenter les données :
» Il n’y a plus de cas à distribuer : toutes les données sont devenues des feuilles de l’arbre.
» La règle de segmentation utilise un nombre de cas inférieur à un plancher prédéfini. Cette
précaution permet d’empêcher l’algorithme de travailler avec des feuilles qui sont peu
représentatives ou trop spécifiques par rapport aux données analysées. Vous évitez ainsi
un sur-ajustement et une variance dans les estimations (voir aussi le Chapitre 18).
» Une des feuilles obtenues possède un nombre de cas inférieur à un plancher, ce qui
constitue une autre précaution pour empêcher la production de règles générales à partir
d’un échantillon trop peu peuplé.

Les arbres de décision ont naturellement tendance à souffrir de sur-ajustement. Vous pouvez
limiter les effets de la variance des estimations en choisissant bien le nombre de segments et
de feuilles terminales. Il est en général conseillé de se limiter à 30 cas (mais cela dépend de la
taille de l’échantillon de départ).
Les arbres de décision sont intuitifs, faciles à comprendre et à visualiser (tout dépendant
encore du nombre de branches et de feuilles). Ils offrent un autre avantage aux datalogues :
aucun traitement ni transformation des données n’est nécessaire, parce que les arbres savent
modéliser les non-linéarités en faisant des approximations. Ils acceptent même tous les types
de variables, même les variables catégorielles (une fois encodées avec des codes pour les
différentes classes). Enfin, les arbres de décision savent gérer les données absentes. Il suffit
d’affecter aux cas absents une valeur inhabituelle, très grande ou négative (selon la façon dont
les cas absents sont distribués). Les arbres de décision sont également peu impactés par les
données aberrantes.

Polyvalence des arbres de décision


Les arbres conçus pour trouver des classes (des attributs, des qualités ou des traits pour
identifier les groupes) sont des arbres de classification. Les arbres qui produisent des
estimations sont des arbres de régression. Voyons un problème de classification à partir du jeu
de données des iris de Fisher, déjà rencontré dans plusieurs chapitres :

from sklearn.datasets import load_iris


iris = load_iris()
X, y = iris.data, iris.target
features = iris.feature_names

Nous chargeons les données dans X, qui contient les prédicteurs, et dans y, qui contient les
classifications. Nous demandons alors une validation croisée pour vérifier les résultats au
moyen d’arbres de décision :

from sklearn.model_selection import cross_val_score


from sklearn.model_selection import KFold
crossvalidation = KFold(n_splits=5,
shuffle=True,
random_state=1)

Nous allons nous servir de la classe DecisionTreeClassifier. Nous définissons le paramètre


max_depth dans une boucle répétitive pour voir quel effet aura une augmentation de la
complexité de l’arbre résultant. Nous espérons atteindre un point idéal rapidement, après
lequel les performances de la validation croisée devraient décroître à cause du sur-
ajustement :

import numpy as np
from sklearn import tree
for depth in range(1,10):
tree_classifier = tree.DecisionTreeClassifier(
max_depth=depth, random_state=0)
if tree_classifier.fit(X,y).tree_.max_depth < depth:
break
score = np.mean(cross_val_score(tree_classifier,
X, y,
scoring=’accuracy’,
cv=crossvalidation))
print(‘Profondeur (depth): %i Précision (accuracy): %.3f’ % (depth,score))

Le code va descendre dans les niveaux de profondeur de l’arborescence jusqu’à ce que l’arbre
ne puisse plus grandir. Le code va alors indiquer le score de validation croisée en termes de
précision :

Profondeur (depth): 1 Précision (accuracy): 0.580


Profondeur (depth): 2 Précision (accuracy): 0.913
Profondeur (depth): 3 Précision (accuracy): 0.920
Profondeur (depth): 4 Précision (accuracy): 0.940
Profondeur (depth): 5 Précision (accuracy): 0.920

Nous constatons qu’une profondeur de 4 est idéale parce que l’arbre va commencer à sur-
ajuster si nous continuons à descendre. La Figure 20.2 montre à quel point l’arbre résultant
est devenu complexe. C’est une autre illustration de l’avantage du paquetage de visualisation
dtreeviz. Nous voyons immédiatement que l’espèce Setosa se distingue des deux autres. Pour
distinguer entre Versicolor et Virginica, il a fallu segmenter plus en fonction de la largeur et de
la longueur des pétales.

Figure 20.2 : Arbre de classification du jeu de données Iris sur quatre niveaux de profondeur.

Pour obtenir une bonne réduction, donc une simplification, vous donnez au paramètre
min_samples_split la valeur 30. Pour vous épargner les feuilles terminales trop petites, vous
donnez à min_samples_leaf la valeur 10. Les petites feuilles sont ainsi supprimées de l’arbre
résultant, ce qui diminue un peu la précision de la validation croisée mais augmente la
simplicité et la capacité de généralisation de la solution.

tree_classifier = tree.DecisionTreeClassifier(
min_samples_split=30, min_samples_leaf=10,
random_state=0)
tree_classifier.fit(X,y)
score = np.mean(cross_val_score(tree_classifier, X, y,

scoring=’accuracy’,
cv=crossvalidation))
print(‘Précision: %.3f’ % score)

La précision obtenue est inférieure à la précédente, parce que la simplification de l’arbre


entraîne un peu de sous-ajustement dans les données :

Précision: 0.913

Nous pouvons nous servir de l’autre classe, DecisionTreeRegressor, pour modéliser un


problème de régression, par exemple sur le jeu de données immobilières de Boston déjà utilisé
dans plusieurs chapitres. Dans le cadre d’une régression, les feuilles terminales proposent la
moyenne des cas en tant que sorties de prédiction.

from sklearn.datasets import load_boston


boston = load_boston()
X, y = boston.data, boston.target
features = boston.feature_names

from sklearn.tree import DecisionTreeRegressor


regression_tree = tree.DecisionTreeRegressor(
min_samples_split=30, min_samples_leaf=10,
random_state=0)
regression_tree.fit(X,y)
score = np.mean(cross_val_score(regression_tree,
X, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation))
print(‘Erreur carrée moyenne (MSE): %.3f’ % abs(score))

L’erreur carrée absolue de validation croisée pour l’immobilier à Boston vaut alors :

Erreur carrée moyenne (MSE): 22.593

Apprendre dans les forêts


Le terme forêt aléatoire (RF, Random Forest) désigne un algorithme de classification comme de
régression conçu par Leo Breitman et Adele Cutler. Son principe consiste à utiliser tour à tour
un grand nombre d’arbres de décision pour fournir des prédictions plus précises, par réduction
du biais et de la variance des estimations. L’action d’agrégation de plusieurs modèles pour
produire une seule prédiction produit un ensemble de modèles. Plusieurs arbres donnent une
forêt, d’où le terme. La forêt aléatoire n’est pas qu’un modèle d’ensemble : c’est surtout un
algorithme simple à utiliser et prêt à l’emploi qui rend le mécapprentissage accessible aux non-
experts. Voici les étapes principales de l’algorithme de forêt aléatoire permettant d’aboutir à
ses prédictions :
1. Création d’un grand nombre d’arbres de décision tous différents à partir de
sous-ensembles différents des observations et des variables.
2. Réensemencement (bootstrap) du jeu d’observation de chaque arbre par
échantillonnage des données originales avec remplacements. En conséquence, la
même observation peut se présenter plusieurs fois dans un jeu de données.
3. Sélection aléatoire pour utilisation d’une partie des variables de chaque arbre.
4. Évaluation des performances de chaque arbre à partir des observations qui
avaient été exclues de l’échantillonnage, ce qui correspond à une estimation hors
du sac (OOB – Out Of Bag).
5. Production de la prédiction finale en tant que moyenne des estimations pour une
régression ou de la classe la plus fréquente pour une prédiction, après avoir
ajusté et exploité tous les arbres pour cette prédiction.
Cette approche permet de réduire le biais parce que les arbres de décision sont bien ajustés
par rapport aux données. En outre, les répartitions étant complexes, elles permettent d’obtenir
une approximation des relations complexes entre les prédicteurs et le résultat prédit.
Normalement, les arbres de décision produisent une variance d’estimation importante, mais
vous pouvez la limiter en produisant la moyenne à partir de plusieurs arbres. Les prédictions
découlant du bruit, qui sont liées à la variance, ont tendance à être réparties de façon
équilibrée au-dessus et en dessous de la valeur que vous avez besoin de prédire. En produisant
une moyenne, ces valeurs en excursion tendent à s’annuler, ce qui donne finalement une
prédiction moyenne plus proche de la valeur correcte théorique.
Leo Breitman a produit le concept de forêt aléatoire en se basant sur la technique d’ensachage
appelée Bagging (en réalité, le terme bagging est une contraction de Bootstrap AGGregatING,
ou agrégation de l’ensemencement). La librairie Scikit-learn propose une classe de Bagging
pour la régression (BaggingRegressor) et une pour la classification (BaggingClassifier).
Vous pouvez combiner l’une ou l’autre avec n’importe quel prédicteur que vous sélectionnez
dans l’un des modules de Scikit-learn. Vous décidez de la proportion de cas et de variables
échantillonnées au moyen des deux paramètres max_samples et max_features. Il ne s’agit pas
ici d’un ensemencement Bootstrap mais d’un échantillonnage ; vous ne pouvez utiliser chaque
cas qu’une fois. C’est ainsi que vous construisez chacun des modèles de l’ensemble. Le
paramètre n_estimators permet de choisir le nombre de modèles. L’exemple suivant exploite
le jeu de données des chiffres manuscrits que nous allons réutiliser dans les sections suivantes.
L’ajustement est réalisé par la méthode d’ensachage Bagging :

from sklearn.datasets import load_digits


digit = load_digits()
X, y = digit.data, digit.target

from sklearn.ensemble import BaggingClassifier


from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
tree_classifier = DecisionTreeClassifier(random_state=0)
crossvalidation = KFold(n_splits=5, shuffle=True,
random_state=1)
bagging = BaggingClassifier(tree_classifier,
max_samples=0.7,
max_features=0.7,
n_estimators=300)
scores = np.mean(cross_val_score(bagging, X, y,
scoring=’accuracy’,
cv=crossvalidation))
print (‘Précision: %.3f’ % scores)

Voici la précision de validation croisée pour un ensachage appliqué aux chiffres manuscrits :

Précision: 0.964

Aussi bien pour l’ensachage que pour la forêt aléatoire, plus il y a de modèles dans l’ensemble,
mieux c’est. Vous ne craignez pas de sur-ajustement parce que chacun des modèles diffère des
autres et les erreurs vont se répartir autour de la valeur réelle. Plus de modèles signifie plus de
stabilité du résultat.
Un autre aspect de cet algorithme est qu’il permet d’estimer l’importance des variables, en
prenant en compte la présence de tous les autres prédicteurs. Vous pouvez ainsi repérer les
caractéristiques importantes dans la prédiction de la cible, compte-tenu de la palette de
caractéristiques disponibles. Vous pouvez vous servir de cette estimation comme règle pour
sélectionner les variables.
À la différence des arbres de décisions isolés, vous ne pouvez pas facilement visualiser ni
décortiquer le fonctionnement d’une forêt aléatoire qui ressemble plutôt à une boîte noire.
Vous ne connaissez que les entrées et les sorties. En raison de cette opacité, le seul moyen de
comprendre comment l’algorithme travaille par rapport aux caractéristiques est cette
estimation de l’importance des variables.
Vous obtenez l’estimation d’importance dans une forêt aléatoire d’une façon très simple : après
avoir construit chacun des arbres, le code charge dans chacune des variables des données
factices, puis évalue à quel point le pouvoir de prédiction décroît. Si la variable est très
influente, cette opération a un effet négatif sensible sur la prédiction. Si l’opération ne change
quasiment rien au résultat, c’est que la variable n’est pas influente.
Classifieurs à forêt aléatoire
Nous conservons notre jeu de chiffres manuscrits pour tester un classifieur à forêt aléatoire :

X, y = digit.data, digit.target
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
crossvalidation = KFold(n_splits=5, shuffle=True,
random_state=1)
RF_cls = RandomForestClassifier(n_estimators=300,
random_state=1)
score = np.mean(cross_val_score(RF_cls, X, y,
scoring=’accuracy’,
cv=crossvalidation))
print(‘Précision: %.3f’ % score)

La précision de validation croisée montre une amélioration par rapport à la méthode à


ensachage de la section précédente :

Précision: 0.977

Dans la plupart de vos projets, il vous suffira de bien choisir le nombre d’estimateurs, en visant
la plus grande valeur possible compte tenu des possibilités de traitement de la machine.
Voyons cela en calculant puis en visualisant une courbe de validation pour l’algorithme :

from sklearn.model_selection import validation_curve


param_range = [10, 50, 100, 200, 300, 500, 800, 1000, 1500]
crossvalidation = KFold(n_splits=3,
shuffle=True,
random_state=1)
RF_cls = RandomForestClassifier(n_estimators=300,
random_state=0)
train_scores, test_scores = validation_curve(RF_cls, X, y,
‘n_estimators’,
param_range=param_range,
cv=crossvalidation,
scoring=’accuracy’)
mean_test_scores = np.mean(test_scores, axis=1)

import matplotlib.pyplot as plt


%matplotlib inline
plt.plot(param_range, mean_test_scores,
‘bD-.’, label=’Score CV’)
plt.grid()
plt.xlabel(‘Nombre d\’estimateurs’)
plt.ylabel(‘Précision’)
plt.legend(loc=’lower right’, numpoints= 1)
plt.show()

La Figure 20.3 montre les résultats de ce traitement. Plus nous avons d’estimateurs, meilleurs
sont les résultats. Vous remarquez qu’à partir d’un certain point, le gain devient vraiment
minimal.
Figure 20.3 : Vérification de l’impact du nombre d’estimateurs dans une forêt aléatoire.

Régresseurs à forêt aléatoire


La classe RandomForestRegressor s’utilise de façon similaire à sa collègue destinée à la
classification ; elle utilise les mêmes paramètres :

X, y = boston.data, boston.target
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
RF_rg = RandomForestRegressor (n_estimators=300,
random_state=1)
crossvalidation = KFold(n_splits=5, shuffle=True,
random_state=1)
score = np.mean(cross_val_score(RF_rg, X, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation))
print(‘Erreur carrée moyenne: %.3f’ % abs(score))

Voici les erreurs carrées moyennes de validation croisée que nous obtenons :

Erreur carrée moyenne: 12.028

L’approche de forêt aléatoire se base sur des arbres de décision. Un arbre de décision
segmente le jeu de données en petits segments correspondant à des feuilles, et ceci pour
estimer les valeurs de régression. La forêt aléatoire exploite la moyenne des valeurs de chaque
feuille pour produire une prédiction. Cette technique fait disparaître des prédictions. Les
valeurs extrêmes ou très élevées disparaissent suite à la création de la moyenne dans chaque
feuille. Le résultat est une série de valeurs amorties à la place des valeurs très élevées ou très
basses.

Optimisation d’une forêt aléatoire


Les modèles de type forêt aléatoire fonctionnent assez bien sans être optimisés et sans
demander de chercher à régler et empêcher le sur-ajustement. En effet, plus vous utilisez un
grand nombre d’estimateurs, meilleurs seront les résultats. Ils ne dépendent que de vous et de
vos ressources de traitement. Il est toujours possible d’améliorer les performances en
éliminant les variables redondantes et pauvres en informations, en ajustant la taille minimale
des feuilles et en choisissant une valeur d’échantillonnage permettant de ne pas avoir trop de
prédicteurs corrélés dans l’échantillon. L’exemple suivant procède à ces réglages :

from sklearn.ensemble import RandomForestClassifier


from sklearn.model_selection import KFold
X, y = digit.data, digit.target
crossvalidation = KFold(n_splits=5, shuffle=True,
random_state=1)
RF_cls = RandomForestClassifier(random_state=1)
scorer = ‘accuracy’

Nous utilisons toujours le jeu de chiffres manuscrits et démarrons avec un classifieur par
défaut. Nous optimisons max_features et min_samples_leaf. Pour max_features, nous allons
sélectionner des options préconfigurées (auto pour avoir toutes les caractéristiques, sqrt ou
log2 pour appliquer une fonction au nombre de caractéristiques). Nous procéderons à une
réintégration avec un petit nombre de caractéristiques en utilisant un tiers de celles-ci. En
limitant le nombre de caractéristiques échantillonnées, nous réduisons le nombre
d’occurrences de sélection simultanée de deux variables corrélées similaires, ce qui améliore
les performances de prédiction.
Une raison statistique invite à optimiser min_samples_leaf. En laissant apparaître des feuilles
avec peu de cas, vous favorisez un sur-ajustement pour des combinaisons données très
particulières. Pour bénéficier d’un minimum de confiance statistique de fidélité des motifs de
données à des règles générales, il est conseillé de demander au moins 30 observations par
feuille. Voici donc le code source de notre optimisation :

from sklearn.model_selection import GridSearchCV


max_features = [X.shape[1]//3, ‘sqrt’, ‘log2’, ‘auto’]
min_samples_leaf = [1, 10, 30]
n_estimators = [50, 100, 300]
search_grid = {‘n_estimators’:n_estimators,
‘max_features’: max_features,
‘min_samples_leaf’: min_samples_leaf}
search_func = GridSearchCV(estimator=RF_cls,
param_grid=search_grid,
scoring=scorer,
cv=crossvalidation)
search_func.fit(X, y)
best_params = search_func.best_params_
best_score = search_func.best_score_
print(‘Meilleurs paramètres: %s’ % best_params)
print(‘Meilleure précision : %.3f’ % best_score)

Nous pouvons ainsi découvrir les meilleurs paramètres et la meilleure précision. Le paramètre
à régler encore correspond au nombre d’arbres :

Best parameters: {‘max_features’: ‘sqrt’,


‘min_samples_leaf’: 1,
‘n_estimators’: 100}
Best accuracy: 0.978

Prédictions par boosting


Exploiter toute une collection d’arbres n’est pas la seule technique d’ensemble envisageable.
Vous disposez aussi de la technique progressive appelée boosting qui utilise également le
concept d’ensemble en faisant croître de nombreux arbres de façon séquentielle. Chaque arbre
de cette forêt essaye de construire un modèle qui va réussir à prédire ce que les arbres
précédents n’ont pas réussi à trouver. Les modèles sont ensuite fusionnés pour pouvoir utiliser
une moyenne pondérée ou un vote majoritaire pondéré pour obtenir la prédiction finale.
Nous allons découvrir deux applications Boosting nommées Adaboost et les machines GBM
(Gradient Boosting Machine). Toute cette famille d’algorithmes permet tant les régressions que
les classifications. Nous commençons par un exemple de classification que nous alimentons
avec notre jeu de chiffres manuscrits, comme pour les forêts aléatoires.
Si vous avez pratiqué les exemples précédents et donc chargé les données avec load_digits()
dans la variable digit, il suffit de réaffecter les variables X et y, comme ceci :

X, y = digit.data, digit.target

Prédicteurs faibles : la force du nombre


La classe AdaBoostClassifier travaille avec une séquence de prédicteurs faibles. C’est le
choix par défaut avec les arbres de décision, mais vous pouvez opter pour un autre algorithme
grâce au paramètre base_estimator. En théorie, un prédicteur faible ne donne généralement
pas de bons résultats parce qu’il souffre de trop de variance ou de biais, et ne produit des
prédictions qu’à peine supérieures au simple hasard. Un bon exemple d’apprenant faible est
l’arbre de décision ne comportant qu’un niveau appelé souche de décision (stump). Ce sont les
arbres de décision qui constituent la meilleure option en termes de performances dans
l’approche Boosting. Vous pouvez donc sans souci exploiter l’apprenant actif par défaut et vous
concentrer sur les deux paramètres importants qui mènent à de bonnes prédictions :
n_estimators et learning_rate.
Le paramètre learning_rate contrôle l’importance de la contribution de chaque prédicteur
faible au résultat final. Une valeur élevée pour ce taux d’apprentissage réclame un moins
grand nombre d’estimateurs n_estimators avant de converger vers une solution optimale, qui
ne sera cependant pas la meilleure en général. Un taux d’apprentissage faible réclame un
temps d’entraînement plus long, car il faut plus de prédicteurs pour atteindre la solution. Par
ailleurs, le sur-ajustement survient moins vite.
Contrairement à l’ensachage (bagging), l’approche Boosting peut entraîner un sur-ajustement
si vous demandez trop d’estimateurs. Il est toujours conseillé de réaliser une validation croisée
pour trouver la bonne valeur, en n’oubliant pas qu’un taux d’apprentissage faible va sur-ajuster
moins vite. Il est en général plus facile de choisir la valeur quasi optimale en réalisant une
recherche à mailles larges.

from sklearn.ensemble import AdaBoostClassifier


from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
ada = AdaBoostClassifier(n_estimators=1000,
learning_rate=0.01,
random_state=1)
crossvalidation = KFold(n_splits=5, shuffle=True,
random_state=1)
score = np.mean(cross_val_score(ada, X, y,
scoring=’accuracy’,
cv=crossvalidation))
print(‘Précision: %.3f’ % score)

Voici la précision en validation croisée que nous obtenons :

Précision: 0.754

Notre exemple utilise l’estimateur par défaut, qui correspond à un arbre de décision complet.
Si vous voulez essayer une simple souche (qui n’a pas besoin d’autres estimateurs), vous devez
instancier la classe AdaBoostClassifier ainsi :

base_estimator=DecisionTreeClassifier(max_depth=1)

Classifieurs à gradient boosting (GBM)


Les machines à GBM (Gradient Boosting Machine) sont encore plus efficaces que le premier
algorithme de boosting, AdaBoost. GBM utilise une optimisation consistant à pondérer les
estimateurs suivants. Comme dans l’exemple précédent, nous partons du jeu de chiffres
manuscrits pour découvrir les paramètres spécifiques d’une machine GBM :

X, y = digit.data, digit.target
crossvalidation = KFold(n_splits=5,
shuffle=True,
random_state=1)

Nous définissons bien sûr ici aussi le taux d’apprentissage et le nombre d’estimateurs (ce sont
des paramètres essentiels pour un apprentissage sans sur-ajustement). Nous devons en outre
fournir les valeurs pour les paramètres subsample et max_depth. Le paramètre subsample
réalise un sous-échantillonnage pendant l’entraînement, pour qu’il soit effectué sur un jeu de
données différent à chaque tour, comme c’est le cas dans la technique d’ensachage bagging. Le
paramètre max_depth stipule le nombre maximal de niveaux des arbres. En pratique, vous
pouvez commencer par trois niveaux, mais il faudra peut-être augmenter cette valeur dans le
cas d’une modélisation de données complexes :

from sklearn.ensemble import GradientBoostingClassifier


from sklearn.model_selection import cross_val_score
GBC = GradientBoostingClassifier(n_estimators=300,
subsample=1.0,
max_depth=2,
learning_rate=0.1,
random_state=1)
score = np.mean(cross_val_score(GBC, X, y,
scoring=’accuracy’,
cv=crossvalidation))
print(‘Précision: %.3f’ % score)

À partir du même problème, GradientBoostingClassifier produit le score de précision


suivant :

Précision: 0.972

Régresseurs à gradient boosting (GBR)


Les régresseurs à gradient boosting (GBR) ne se distinguent pas particulièrement de leurs
collègues classifieurs GBC. La principale différence correspond aux multiples fonctions de
perte loss utilisables. Rappelons que le classifieur GradientBoostingClassifier, ne propose
qu’une fonction de perte de déviance (ressemblant en cela à la fonction de coût d’une
régression logistique).

from sklearn.ensemble import GradientBoostingRegressor


from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
GBR = GradientBoostingRegressor(n_estimators=1000,
subsample=1.0,
max_depth=3,
learning_rate=0.01,
random_state=1)
crossvalidation = KFold(n_splits=5,
shuffle=True,
random_state=1)
score = np.mean(cross_val_score(GBR, X, y,
scoring=’neg_mean_squared_error’,
cv=crossvalidation))
print(‘Erreur carrée moyenne: %.3f’ % abs(score))

Nous obtenons une erreur carrée moyenne de régression bien meilleure que celle produite par
une forêt aléatoire :

Erreur carrée moyenne: 1.499

Notre exemple entraîne un régresseur GradientBoostingRegressor avec la valeur ls par


défaut pour le paramètre de perte loss, ce qui correspond à une régression linéaire. D’autres
choix sont possibles :
» quantile : devine un quantile spécifique au moyen du paramètre alpha (en général 0.5,
la médiane).
» lad (Least Absolute Deviation) : valeur donnant une grande robustesse aux données
aberrantes et tendant à ranger les prédictions correctement de façon ordinale.
» huber : combinaison des valeurs ls et lad qui vous demande de définir le paramètre
alpha.

Hyperparamètres de GBM
Les modèles à machine GBM sont assez sensibles aux sur-ajustements s’il y a trop
d’estimateurs en séquence et si le modèle commence à ajuster sur le bruit des données. Il faut
donc vérifier l’efficacité des valeurs couplées pour le nombre d’estimateurs et le taux
d’apprentissage. L’exemple suivant part du jeu de données immobilières de Boston :

X, y = boston.data, boston.target
from sklearn.model_selection import KFold
crossvalidation = KFold(n_splits=5, shuffle=True,
random_state=1)
GBR = GradientBoostingRegressor(n_estimators=1000,
subsample=1.0,
max_depth=3,
learning_rate=0.01,
random_state=1)

Sachez que l’optimisation peut prendre un certain temps parce que les algorithmes GBM
sollicitent beaucoup la machine, surtout si vous choisissez une valeur élevée pour max_depth.
Une bonne technique consiste à ne pas modifier le taux d’apprentissage tout en cherchant à
optimiser subsample et max_depth par rapport à n_estimators (rappelons que de grandes
valeurs pour max_depth supposent en général un moins grand nombre d’estimateurs). Une fois
que vous avez trouvé les meilleures valeurs pour subsample et pour max_depth, vous pouvez
alors pousser plus loin l’optimisation avec n_estimators et learning_rate.

from sklearn.model_selection import GridSearchCV


subsample = [1.0, 0.9]
max_depth = [2, 3, 5]
n_estimators = [500 , 1000, 2000]
search_grid = {‘subsample’: subsample,
‘max_depth’: max_depth,
‘n_estimators’: n_estimators}
search_func = GridSearchCV(estimator=GBR,
param_grid=search_grid,
scoring=’neg_mean_squared_error’,
cv=crossvalidation)
search_func.fit(X,y)

best_params = search_func.best_params_
best_score = abs(search_func.best_score_)
print(‘Meilleurs param.: %s’ % best_params)
print(‘Meilleure erreur carrée moyenne: %.3f’ % best_score)

Une fois l’optimisation réalisée, nous étudions l’erreur carrée moyenne et constatons
l’amélioration par rapport au paramétrage par défaut. Les machines GBM ont toujours besoin
d’un paramétrage fin pour produire leurs meilleurs résultats :

Meilleurs param.: {‘max_depth’: 3, ‘n_estimators’: 2000, ‘subsample’: 0.9}


Meilleure erreur carrée moyenne: 9.324
PARTIE 6
Les dix commandements

DANS CETTE PARTIE :


» Poursuivre votre formation en ligne
» Trouver des gisements de données
» Localiser et participer à des challenges de données
Chapitre 21
Dix ressources
DANS CE CHAPITRE :
» Pour bien continuer

» Sites pédagogiques

» Ressources

N. d. T. : Nous fournissons avec le fichier archive des exemples un fichier au format texte
contenant les liens mentionnés dans les deux chapitres de cette partie.

Arrivé à la fin de ce livre, vous avez découvert un certain nombre de sujets concernant la
science des données avec Python. Mais ce n’est que la partie émergée de l’iceberg. Une
quantité phénoménale de ressources sont offertes à vos quêtes.
Certains sites présentés se résument à une liste de liens sélectionnés. D’autres présentent des
exposés complets, et d’autres encore proposent des outils de datalogie.
Tout bouge sans cesse sur Internet, et certains liens pourront devenir caducs lorsque vous lirez
ceci. C’est ainsi. Nous les revérifierons lors de la prochaine édition.

N. d. T. : Nous avons ajouté quelques sites français par rapport à la version originale.

L’actualité avec ActuIA


(en français) Le site ActuIA est bien alimenté en informations techniques et événementielles.
Vous y trouverez une foule de liens et un agenda des prochaines occasions de rencontrer
d’autres datalogues et partenaires :
https://www.actuia.com

La recherche avec la SSFAM


(en français) La Société Savante Francophone d’Apprentissage Machine qui compte
déjà 300 membres regroupe chercheurs et enseignants.
http://ssfam.org

La datalogie à Paris-Saclay
(en français) Le plateau de Saclay à 20 km au sud-ouest de Paris devient un pôle d’excellence
en réunissant établissements publics de recherche, entreprises technologiques, grandes écoles
et université. Deux liens pour accéder au plateau :
https://www.universite-paris-saclay.fr/fr/bdbc/data-sciences-cartographie-des-
45-formations
https://www.datascience-paris-saclay.fr/

Data Analytics Post


(en français) Malgré son titre, ce site est maintenu en région parisienne. Se présentant comme
média d’information et de réflexion autour de la datalogie, il aborde tous les sujets liés, de
l’agriculture à la cybersécurité.
https://dataanalyticspost.com/

Le blog Data Science d’Oracle


(en anglais) Les grands fournisseurs d’informatique peuvent publier beaucoup d’informations.
Vous n’oublierez cependant pas qu’elles sont formulées dans l’intérêt de leur éditeur. Le blog
de Data Science Oracle (https://www.datascience.com/blog) regorge d’articles techniques
d’analyse et de stratégie de réduction des coûts.

Ressources de Data Science Central


(en anglais) Le site Data Science Central (https://www.datasciencecentral.com/) se
distingue en offrant un accès à un grand nombre d’experts en datalogie qui présentent des
sujets beaucoup moins courants que sur d’autres sites. Il héberge notamment un blog très
intéressant qui collecte des ressources techniques :
https://www.datasciencecentral.com/profiles/blogs/huge-trellolist-of-great-data-
science-resources
Il s’agit d’une liste au format calendrier de Trello (https://trello.com/). Parmi les catégories
proposées, citons les actualités, les personnes clés, les journalistes, les padawans, les
scientifiques, les langes R et Python et le Big data.

24 sites de choix chez Udacity


(en anglais) Il n’est pas toujours évident de trouver les sites les plus en rapport avec le besoin
du moment. Cette page, hébergée par le site de cours en ligne Udacity, propose plus
de 24 sites de qualité :
https://blog.udacity.com/2014/12/24-data-science-resources-keep-finger-
pulse.html

Open Source Data Science Masters


(en anglais) De plus en plus d’entreprises s’intéressent aux solutions de datalogie open source.
Un catalogue de ressources intitulé « Open Source Data Science Masters » (OSDSM) a été
créé sur le site http://datasciencemasters.org. Son but est de vous fournir des liens vers
toutes les ressources qui semblent manquer aux cursus traditionnels de formation initiale afin
de renforcer votre employabilité. (N. d. T. : l’équivalent en langue française devrait apparaître
sous peu.)

Ressources pour développeurs par Jonathan Bower


(en anglais) Le référentiel GitHub (https://github.com/) est une place d’échange pour
collaborer, réviser le code source et en gérer les versions. Un des sites hébergés est celui de
Jonathan Bower. Vous y trouverez une foule de liens vers des ressources en datalogie :
https://github.com/jonathan-bower/DataScienceResources
Elles sont d’abord orientées développeurs, mais vous y trouverez quelques pépites, même sans
être anglophone. Parmi les catégories proposées, citons :
» Data science, getting started
» Data pipeline and tools
» Product
» Career resources
» Open source data science resources

Tout est organisé en catégories, thèmes et sujets, facilitant la navigation. Par exemple, la
catégorie Data Pipeline & Tools, donne sur Python, qui donne sur un lien pour les débutants
Anyone Can Code.
Vous y trouverez aussi les liens vers les grands sites américains de diffusion de cours en ligne
MOOC que sont Coursera, Udacity et Edx.
Pour les MOOC en français, voyez ces sites :
» https://www.fun-mooc.fr/
» https://www.my-mooc.com/fr/categorie/data-science

Quelques autres sites


(en anglais)
Subreddit : https://www.reddit.com/r/datascience/
KDnuggets : https://www.kdnuggets.com/faq/learning-data-mining-data-science.html
The Aspirational Data Scientist : https://newdatascientist.blogspot.com/
The Conductrics : https://conductrics.com/
L’auteur du blog sur le site de Conductrics, Matt Gershoff, anime un forum bien fourni
consacré à l’apprentissage machine. Y sont abordées de nombreuses techniques : Latent
Semantic Indexing (LSI), Single Value Decomposition (SVD), Linear Discriminant Analysis
(LDA), approches bayésiennes non paramétriques, traduction statistique, apprentissage
renforcé Reinforcement Learning (RL), apprentissage par différence temporelle Temporal
Difference (TD) et même les problèmes du bandit manchot (context bandits).
Chapitre 22
Challenges et gisements de données
DANS CE CHAPITRE :
» Localiser des sites proposant des challenges

» Accéder à des bases de données

» Découvrir des techniques de pointe

ar définition, la datalogie (science des données) s’intéresse aux données. Tout au long de ce
P livre, nous avons utilisé un certain nombre de jeux de données, et notamment ceux fournis
pour expérimentation dans la librairie Scikit-learn. Ces jeux sont parfaits pour débuter, mais
puisque vous voulez certainement poursuivre votre aventure, il vous faut trouver de nouvelles
sources de données plus vastes.

Les pages qui suivent montrent comment accéder à un certain nombre de défis liés à des jeux
de données, et conçus pour faire de vous un datalogue de classe mondiale. En combinant les
compétences acquises dans ce livre avec ces jeux de données, vous allez réaliser des projets
incroyables. Vous passerez peut-être même pour une sorte de magicien en extrayant des
informations étonnantes dans ces jeux de données. Tout ce qui suit permet d’acquérir des
compétences particulières et de réussir dans les projets les plus divers.
&e réseau Internet regorge de jeux de données, mais tous ne sont pas du même niveau.
Choisissez vos défis avec soin. Ceux que nous présentons ici sont souvent accompagnés de
tutoriels et sont liés à des publications scientifiques. Les compétitions proposées sont
considérées comme de haut niveau. En relevant ces défis, vous pourrez par exemple ensuite
vous attaquer à l’extraction d’informations depuis une base de données d’entreprise.

Challenge Data Science London + Scikit-learn


Le site Kaggle (https://www.kaggle.com) avait organisé en 2013 une compétition utilisant les
algorithmes de classification Scikit-learn dont vous pouvez voir les résultats (concours clos
en 2014) en visitant la page suivante :
https://www.kaggle.com/c/data-science-london-Scikit-learn
Les données sont disponibles ici :
https://www.kaggle.com/c/data-science-london-scikit-learn/data
Les règles sont rappelées ici :
https://www.kaggle.com/c/data-science-london-scikit-learn/rules
La lecture des noms des participants pourrait vous décourager de participer à ce genre de
concours, car vous y trouverez des datalogues de réputation mondiale. N’hésitez pas à
parcourir les pages du site pour récupérer les données et tenter de résoudre le défi par vous-
même ! Vous n’êtes pas forcé de publier votre solution.
&e concours utilise des connaissances fournies dans ce livre. Il constitue donc un bon point de
départ pour poursuivre votre découverte de la datalogie.

Prédiction de survie sur le Titanic


Vous avez utilisé les données du Titanic dans deux chapitres du livre, en exploitant le fichier
titanic.csv que nous avions préparé à partir des données de l’École de médecine de
l’université de Vanderbilt. Le site Kaggle organise un challenge permanent pour les débutants
en se fondant sur ces données. Voyez la page d’accueil de Kaggle
(https://www.kaggle.com/c/titanic). Le modèle de données se trouve à l’adresse
https://www.kaggle.com/c/titanic/data et les règles à l’adresse
https://www.kaggle.com/c/titanic/rules.
La page du palmarès des meilleurs (leaderboard) permet d’admirer les noms de ceux qui ont
réussi à atteindre un score considéré comme parfait.
Le principal défi est que le jeu de données n’est pas très volumineux, ce qui vous oblige à
générer de nouvelles caractéristiques pour obtenir un bon score. C’est l’occasion d’exercer les
compétences acquises dans la section du Chapitre 9 qui explique comment créer des
caractéristiques, et de revoir la démonstration du Chapitre 19.

Recherche d’une compétition Kaggle


Les compétitions sont intéressantes, parce qu’elles vous poussent à chercher une solution en
même temps que d’autres personnes. Dans la vie réelle, vous serez souvent en concurrence et
l’esprit de compétition permet de gagner vite de l’expérience et d’apprendre à penser vite et à
acquérir un sens critique. Bien sûr, c’est aussi l’occasion d’apprendre à travers les autres. Un
excellent site organisant des compétitions est Kaggle, déjà mentionné.
(https://www.kaggle.com/competitions).
La section Compétitions présente tout d’abord les compétitions en cours, actives. Celles qui
sont terminées sont plus bas dans la même liste. Tous les jeux de données sont librement
disponibles. Vous pouvez donc tenter votre chance. La communauté qui fréquente le site offre
des tutoriels, des bancs d’essai et des incitations à faire progresser vos scores.
Vous pouvez tout à fait vous lancer dans une des nombreuses compétitions terminées.
L’avantage est que vous aurez accès aux solutions publiées. Si vous optez pour une compétition
en cours, vous pourrez poser vos questions sur le forum et recevrez les réponses d’éminents
datalogues. Vous trouverez certainement une compétition qui correspond à vos besoins.
Notez que les compétitions publiées sont lancées par des entreprises qui n’ont normalement
pas accès à des datalogues. Vous allez donc travailler dans un environnement réel. Vous
pourrez profiter de l’occasion pour trouver un emploi. Rendez par exemple visite à la page Jobs
du site.

Tous contre le sur-apprentissage !


Le jeu de données Madelon (https://archive.ics.uci.edu/ml/datasets/Madelon) contient
des données artificielles qui proposent un problème de classification de classe avec des
variables d’entrée continues. C’est un excellent exercice pour tester vos compétences en
validation croisée de modèles. L’objectif premier est d’éviter le sur-ajustement ou sur-
apprentissage, problème que nous avons rencontré dans les Chapitres 16, puis 18 à 20. Vous
récupérez directement les fichiers par le lien Data Folder de la page mentionnée.
Certains des participants à ce challenge ont produit des articles décrivant leur méthode. Les
meilleurs de ces articles ont été réunis dans un livre intitulé Feature Extraction, Foundations
and Applications publié par Springer (https://www.springer.com/us/book/9783540354871).
La page Web de Madelon comporte aussi un lien vers un rapport technique écrit par Isabelle
Guyon et un autre lien qui donne accès à une foule de rapports concernant le NIPS (Neural
Information Processing Systems).

MovieLens : offrez-vous une toile !


Le site MovieLens (https://movielens.org/) vous propose de trouver tous les films que vous
aimeriez voir parmi les millions de films déjà tournés ; normalement, trouver ceux qui vous
plairont peut prendre beaucoup de temps. Cette base de données vous demande de noter les
films que vous avez déjà vus, puis répond avec des recommandations appropriées. Vos notes
servent à un algorithme à apprendre, puis à chercher dans toute la base. Vous récupérez cette
base de données ici :
https://grouplens.org/datasets/movielens/
La base est fournie en plusieurs tailles, et notamment :
» 100 000 notations par 2 000 utilisateurs pour 1 700 films ;
» 1 million de notations par 6 000 utilisateurs pour 4 000 films ;
» 10 millions de notations et 100 000 tags par 70 000 utilisateurs pour 10 000 films ;
» 20 millions de notations et 5 000 tags par 130 000 utilisateurs pour 27 000 films.
» La base est mise à jour en continu. Au moment d’écrire ce livre, la base complète
contenait 21 millions d’avis par 230 000 utilisateurs pour 27 000 films !

Cette base permet de s’entraîner à traiter des données produites par des utilisateurs, aussi
bien en mode supervisé que non supervisé. Nous avons abordé ces deux modes dans les
Chapitres 15 et 19. Cet énorme volume de données vous donne l’occasion de vous confronter
réellement à l’exploitation des mégadonnées.

Des courriels sans pourriels !


Nous sommes tous gênés par les messages électroniques indésirables, c’est-à-dire les
pourriels. La vraie solution est un filtre basé sur un algorithme, mais cet algorithme doit être
entraîné. C’est à cela que sert une base de données telle que celle de Spambase, que vous
pouvez récupérer à l’adresse suivante :
https://archive.ics.uci.edu/ml/datasets/Spambase
Cette collection de pourriels a été recueillie auprès de responsables de messagerie et
d’individus qui leur avaient envoyé un rapport de spam. La base contient également des
courriels désirables, ce qui permet de tester la qualité du filtrage. Notez que ce challenge est
complexe, car il s’agit de gérer des données textuelles et des cibles variées et complexes.

Écriture manuscrite
La reconnaissance de motifs (patterns), notamment ceux de l’écriture manuscrite, est un
domaine de datalogie majeur. Le jeu de données des chiffres manuscrits de l’institut NIST est
disponible sur le site du chercheur français Yann Le Cun (parti vivre aux USA) à l’adresse
suivante :
http://yann.lecun.com/exdb/mnist/
Il s’agit d’un sous-ensemble du jeu complet, ne contenant que 60 000 exemples avec un jeu de
test de 10 000. Le jeu complet du NIST est disponible par un lien sur la même page. Le jeu
réduit donne l’occasion de traiter les données manuscrites sans avoir à réaliser d’abord de
lourds prétraitements.
Le jeu est constitué de quatre fichiers, pour l’entraînement et les tests, avec les images et les
labels. Vous devez bien sûr récupérer ces quatre fichiers. Un problème de ce jeu NIST est que
les fichiers d’images ne sont pas dans un format unique (le format est présenté en bas de la
page Web). Plutôt que d’écrire vous-même le code Python permettant de charger ces images,
n’hésitez pas à récupérer les solutions publiées par d’autres. Voici trois sites permettant de
récupérer le code pour lire ce jeu de données en Python :
https://cs.indstate.edu/⁓jkinne/cs475-f2011/code/mnistHandwriting.py
https://martin-thoma.com/classify-mnist-with-pybrain/
https://gist.github.com/akesling/5358964
La page consacrée à cette base contient une liste de méthodes à appliquer aux deux jeux. Vous
pourrez y découvrir un nombre étonnant de classificateurs qui vous donneront des idées pour
résoudre le problème. Ce jeu de données se montrera utile dans toutes sortes d’activités.
Vous avons utilisé le jeu d’expérimentation de chiffres manuscrits fourni dans Scikit-learn tout
au long de ce livre. Ce jeu de données est beaucoup moins volumineux, ce qui nous a permis
d’avancer vite parmi les exemples des chapitres concernés.

Analyse de photographies
L’institut de recherche canadien CIFAR propose des jeux de données graphiques à l’adresse
https://www.cs.toronto.edu/⁓kriz/cifar.html sous forme de sous-ensembles du jeu
complet (qui réunit plus de 80 millions de petites images). Le jeu CIFAR-10 ne retient
que 60 000 images en couleurs de 32 sur 32 réparties en dix classes (6 000 images dans
chacune). Ces dix classes correspondent à quatre classes de mode de transport (avions,
automobiles, bateaux, camions) et six classes d’animaux (oiseaux, chats, cervidés, chiens,
grenouilles, chevaux).
Le jeu CIFAR-100 contient 100 classes au lieu de 10, avec le même volume, donc 600 images
par classe. La classification est hiérarchique et les classes sont réparties dans 20 superclasses.
Par exemple, la superclasse des mammifères aquatiques réunit les cinq classes castors,
dauphins, loutres, morses et baleines.
La page mentionnée propose le téléchargement en trois versions : Python, Matlab et binaire.
Prenez soin de bien lire les instructions fournies avec le fichier archive. Vous opterez
évidemment pour la version Python.

Ce défi est tout à fait intéressant à relever juste après celui des chiffres manuscrits. Il donne
l’occasion d’apprendre à gérer des images complexes en couleurs. Si vous avez réalisé les
exemples du Chapitre 14, vous avez un peu d’expérience dans le traitement d’images à partir
du jeu d’expérimentation Olivetti Faces.

Analyse des avis sur Amazon.com


Si vous vous sentez prêt à travailler avec un énorme volume de données, plongez-vous dans le
jeu de données des avis laissés sur le site amazon.com. Cette base recueille 10 millions d’avis
classés par catégorie de produits sur une vingtaine d’années. Nous conseillons de vous
entraîner d’abord sur une base telle que celle des films, MovieLens. Rendez-vous ensuite dans
une de ces deux pages :
https://snap.stanford.edu/data/web-Amazon.html
http://jmcauley.ucsd.edu/data/amazon/
Vous avez le choix entre la base complète et les bases par catégorie. Notez que vous devrez
sans doute vous faire connaître auprès du responsable de la maintenance de la base pour
pouvoir l’exploiter.
N’oubliez pas de faire défiler la page mentionnée : vous trouverez tout en bas des conseils pour
exploiter la base en code Python. Vous pourrez bien sûr personnaliser les fonctions proposées,
mais elles serviront d’excellent point de départ.

Plongée dans un réseau de pages Web


Imaginez que vous puissiez travailler à partir des liens établis entre 4 milliards de pages Web !
En effet, quel est le jeu de données le plus riche et le plus complexe, sinon celui qui constitue
Internet lui-même ? Vous commencerez par un sous-ensemble de la base, tel qu’il est proposé
par Common Crawl (https://commoncrawl.org/). Vous apprendrez ainsi à extraire et exploiter
les données des sites Web. Voici les domaines d’emplois principaux de ce jeu de données :
» algorithmes de recherche Web ;
» détection des pourriels ;
» algorithmes d’analyse de réseaux et graphes ;
» recherche en sciences du Web.

Dans la page d’accueil, ouvrez le menu THE DATA et choisissez Get started pour commencer
(https://commoncrawl.org/the-data/get-started/). Une description détaillée du contenu
est fournie à l’adresse suivante :
http://webdatacommons.org/hyperlinkgraph/
Notez que le téléchargement de la base complète (qui grossit jour après jour), représente
notamment 190 Go uniquement pour le fichier des index et, tenez-vous bien, 55 990 Go
(56 téraoctets) pour le seul fichier WARC.
Mais que ces volumes ne vous fassent pas peur. Si vous avez réalisé les exemples du
Chapitre 7, vous savez déjà travailler avec des données de liens de graphes. Le volume à
traiter a bien sûr son importance, mais vous connaissez déjà certaines des techniques requises.
Et maintenant, bon courage dans la suite de vos aventures de datalogue !
Sommaire

Couverture

Python pour la Data science Pour les Nuls


Copyright

Introduction
Contenu du livre

Connaissances préalables supposées

Conventions typographiques du livre

Fichiers source des exemples

Trajectoire de lecture

Les auteurs

Terminologie française

PARTIE 1. Entrée en datalogie avec Python


Chapitre 1. Pourquoi Python convient à la datalogie

Le métier le plus attirant du XXIe siècle

Création du pipeline de datalogie

Rôle de Python en datalogie

Découverte de l’atelier Python

Chapitre 2. Capacités du langage. Python


Pourquoi Python ?

Aperçu de l’utilisation de Python

Prototypage rapide et expérimentation

Considérations de performances

Capacités de visualisation

L’écosystème Python et la datalogie

Chapitre 3. Mise en place de l’atelier Python


Quelques paquetages scientifiques préconfigurés

Installation d’Anaconda sous Windows

Installation d’Anaconda sous Linux

Installation d’Anaconda sous MacOS X

Récupération des données et des exemples

Chapitre 4. Google Colab


Présentation de Google Colab

Accès à un compte Google

Utiliser des calepins (notebooks)

Principales actions Colab

L’accélération matérielle

Exécution du code

Partage d’un projet

L’aide de Colab
PARTIE 2. Plongée dans les données
Chapitre 5. Découverte des outils
La console Jupyter

Utilisation de Jupyter Notebook

Insertion de graphiques et de multimédia

Chapitre 6. Formats de données d’entrée


Téléchargement, flux et échantillons

Accès aux données des fichiers plats

Lecture de fichiers binaires

Bases de données (SGBDR)

Bases NoSQL

Accès aux données du Web

Chapitre 7. Préparation des données


Jonglage entre NumPy et pandas

Validation des données

Utilisation de variables catégorielles

Gestion des données temporelles

Gestion des données manquantes

Tranchage et débitage : filtrer et sélectionner des données

Concaténation et transformation

Agrégation des données par niveau

Chapitre 8. Épuration des données


Traitements HTML et XML

Épuration de fichiers texte bruts

Expressions régulières

Sacs de mots et analyse lexicale

Traitement de données graphiques

Chapitre 9. Discrétisation et tableaux


Établissement du contexte

L’art de créer des caractéristiques

Opérations sur les tableaux et les matrices

PARTIE 3. Visualisation des informations


Chapitre 10. Visite guidée de MatPlotLib
Création d’un premier diagramme

Ajout des axes, des repères et de la grille

Contrôle de l’aspect des lignes

Ajout de labels, d’annotations et de légendes

Chapitre 11. Visualisation des données


Choisir le bon diagramme

Nuages de points avancés

Tracé de séries temporelles

Visualisation de données géographiques

Graphes orientés et non orientés


PARTIE 4. Transformation des données
Chapitre 12. Renforcement des capacités de Python
Découverte de Scikit-learn

La technique de hachage

Considérations de temps et de performances

Exécution parallèle et multicœur

Chapitre 13. Analyse de données exploratoire (EDA)


L’approche EDA

Statistiques descriptives pour données numériques

Mesure des données catégorielles

Visualisation d’analyse exploratoire

Principe de la corrélation

Modification d’une distribution de données

Chapitre 14. Réduction de dimensionnalité


Décomposition en valeurs singulières (SVD)

Analyse de facteurs et PCA

Quelques applications de décomposition

Chapitre 15. Regroupements. (clustering)


Regroupement par k-moyennes

Regroupements hiérarchiques

Détection de nouveaux groupes avec DBScan

Chapitre 16. Détection des données aberrantes


Détection des données aberrantes

Une méthode univariée simple

Approche multivariée

PARTIE 5. Apprendre des données


Chapitre 17. Quatre algorithmes fondamentaux
Régression linéaire et recherche de chiffres

Régressions logistiques et classes

Algorithme naïf bayésien

Méthode KNN (k plus proches voisins)

Chapitre18. Validation croisée, sélectionet optimisation


Maîtrise des problèmes d’ajustement de modèle

Validation croisée

Stratégie de sélection des variables

Optimisation des hyperparamètres

Chapitre 19. Vers plus de complexité linéaire et non linéaire


Transformations non linéaires

Régularisation d’un modèle linéaire

Les mégadonnées bouchée par bouchée

Machines à vecteurs de support (SVM)

Incursion dans les réseaux neuronaux

Chapitre 20. Plus forts à plusieurs


Arbres de décision classiques
Apprendre dans les forêts

Prédictions par boosting

PARTIE 6. Les dix commandements


Chapitre 21. Dix ressources
L’actualité avec ActuIA

La recherche avec la SSFAM

La datalogie à Paris-Saclay

Data Analytics Post

Le blog Data Science d’Oracle

Ressources de Data Science Central

24 sites de choix chez Udacity

Open Source Data Science Masters

Ressources pour développeurs par Jonathan Bower

Quelques autres sites

Chapitre 22. Challenges et gisements de données


Challenge Data Science London + Scikit-learn

Prédiction de survie sur le Titanic

Recherche d’une compétition Kaggle

Tous contre le sur-apprentissage !

MovieLens : offrez-vous une toile !

Des courriels sans pourriels !

Écriture manuscrite

Analyse de photographies

Analyse des avis sur Amazon.com

Plongée dans un réseau de pages Web

Vous aimerez peut-être aussi