Maîtriser les expressions régulières

Les expressions régulières fournissent un cadre très pratique pour manipuler de manière flexible des données textuelles. Elles sont très utiles notamment pour les tâches de traitement naturel du langage (NLP) ou le nettoyage de données textuelles.

Tutoriel
Manipulation
Author

Lino Galiana

Published

2024-07-26

1 Introduction

Python offre énormément de fonctionalités très pratiques pour la manipulation de données textuelles. C’est l’une des raisons de son succès dans la communauté du traitement automatisé du langage (NLP, voir partie dédiée).

Dans les chapitres précédents, nous avons parfois été amenés à chercher des éléments textuels basiques. Cela était possible avec la méthode str.find du package Pandas qui constitue une version vectorisée de la méthode find de base. Nous avons d’ailleurs pu utiliser cette dernière directement, notamment lorsqu’on a fait du web scraping.

Cependant, cette fonction de recherche trouve rapidement ses limites. Par exemple, si on désire trouver à la fois les occurrences d’un terme au singulier et au pluriel, il sera nécessaire d’utiliser au moins deux fois la méthode find. Pour des verbes conjugués, cela devient encore plus complexe, en particulier si ceux-ci changent de forme selon le sujet.

Pour des expressions compliquées, il est conseillé d’utiliser les expressions régulières, ou “regex”. C’est une fonctionnalité qu’on retrouve dans beaucoup de langages. C’est une forme de grammaire qui permet de rechercher des expressions.

Une partie du contenu de cette partie est une adaptation de la documentation collaborative sur R nommée utilitR à laquelle j’ai participé. Ce chapitre reprend aussi du contenu du livre R for Data Science qui présente un chapitre très pédagogique sur les regex.

Nous allons utiliser le package re pour illustrer nos exemples d’expressions régulières. Il s’agit du package de référence, qui est utilisé, en arrière-plan, par Pandas pour vectoriser les recherches textuelles.

import re
import pandas as pd
Tip

Les expressions régulières (regex) sont notoirement difficiles à maîtriser. Il existe des outils qui facilitent le travail avec les expressions régulières.

  • L’outil de référence pour ceci est [https://regex101.com/] qui permet de tester des regex en Python tout en ayant une explication qui accompagne ce test

  • De même pour ce site qui comporte une cheat sheet en bas de la page.

  • Les jeux de Regex Crossword permettent d’apprendre les expressions régulières en s’amusant

Il peut être pratique de demander à des IA assistantes, comme Github Copilot ou ChatGPT, une première version d’une regex en expliquant le contenu qu’on veut extraire. Cela peut faire économiser pas mal de temps, sauf quand l’IA fait preuve d’une confiance excessive et vous propose avec aplomb une regex totalement fausse…

2 Principe

Les expressions régulières sont un outil permettant de décrire un ensemble de chaînes de caractères possibles selon une syntaxe précise, et donc de définir un motif (ou pattern). Les expressions régulières servent par exemple lorsqu’on veut extraire une partie d’une chaîne de caractères, ou remplacer une partie d’une chaîne de caractères. Une expression régulière prend la forme d’une chaîne de caractères, qui peut contenir à la fois des éléments littéraux et des caractères spéciaux qui ont un sens logique.

Par exemple, "ch.+n" est une expression régulière qui décrit le motif suivant : la chaîne littérale ch, suivi de n’importe quelle chaîne d’au moins un caractère (.+), suivie de la lettre n. Dans la chaîne "J'ai un chien.", la sous-chaîne "chien" correspond à ce motif. De même pour "chapeau ron" dans "J'ai un chapeau rond". En revanche, dans la chaîne "La soupe est chaude.", aucune sous-chaîne ne correpsond à ce motif (car aucun n n’apparaît après le ch).

Pour s’en convaincre, nous pouvons déjà regarder les deux premiers cas:

pattern = "ch.+n"
print(re.search(pattern, "J'ai un chien."))
print(re.search(pattern, "J'ai un chapeau rond."))
<re.Match object; span=(8, 13), match='chien'>
<re.Match object; span=(8, 19), match='chapeau ron'>

Cependant, dans le dernier cas, nous ne trouvons pas le pattern recherché :

print(re.search(pattern, "La soupe est chaude."))
None

La regex précédente comportait deux types de caractères:

  • les caractères littéraux : lettres et nombres qui sont reconnus de manière littérale
  • les méta-caractères : symboles qui ont un sens particulier dans les regex.

Les principaux méta-caractères sont ., +, *, [, ], ^ et $ mais il en existe beaucoup d’autres. Parmi cet ensemble, on utilise principalement les quantifieurs (., +, *…), les classes de caractères (ensemble qui sont délimités par [ et ]) ou les ancres (^, $…)

Dans l’exemple précédent, nous retrouvions deux quantifieurs accolés .+. Le premier (.) signifie n’importe quel caractère1. Le deuxième (+) signifie “répète le pattern précédent”. Dans notre cas, la combinaison .+ permet ainsi de répéter n’importe quel caractère avant de trouver un n. Le nombre de fois est indeterminé : cela peut ne pas être pas nécessaire d’intercaler des caractères avant le n ou cela peut être nécessaire d’en intercepter plusieurs :

print(re.search(pattern, "J'ai un chino"))
print(re.search(pattern, "J'ai un chiot très mignon."))
<re.Match object; span=(8, 12), match='chin'>
<re.Match object; span=(8, 25), match='chiot très mignon'>

2.1 Classes de caractères

Lors d’une recherche, on s’intéresse aux caractères et souvent aux classes de caractères : on cherche un chiffre, une lettre, un caractère dans un ensemble précis ou un caractère qui n’appartient pas à un ensemble précis. Certains ensembles sont prédéfinis, d’autres doivent être définis à l’aide de crochets.

Pour définir un ensemble de caractères, il faut écrire cet ensemble entre crochets. Par exemple, [0123456789] désigne un chiffre. Comme c’est une séquence de caractères consécutifs, on peut résumer cette écriture en [0-9].

Par exemple, si on désire trouver tous les pattern qui commencent par un c suivi d’un h puis d’une voyelle (a, e, i, o, u), on peut essayer cette expression régulière.

re.findall("[c][h][aeiou]", "chat, chien, veau, vache, chèvre")
['cha', 'chi', 'che']

Il serait plus pratique d’utiliser Pandas dans ce cas pour isoler les lignes qui répondent à la condition logique (en ajoutant les accents qui ne sont pas compris sinon):

import pandas as pd

txt = pd.Series("chat, chien, veau, vache, chèvre".split(", "))
txt.str.match("ch[aeéèiou]")
0     True
1     True
2    False
3    False
4     True
dtype: bool

Cependant, l’usage ci-dessus des classes de caractères n’est pas le plus fréquent. On privilégie celles-ci pour identifier des pattern complexe plutôt qu’une suite de caractères littéraux. Les tableaux d’aide mémoire illustrent une partie des classes de caractères les plus fréquentes ([:digit:] ou \d…)

2.2 Quantifieurs

Nous avons rencontré les quantifieurs avec notre première expression régulière. Ceux-ci contrôlent le nombre de fois qu’un pattern est rencontré.

Les plus fréquents sont:

  • ? : 0 ou 1 match ;
  • + : 1 ou plus de matches ;
  • * : 0 or more matches.

Par exemple, colou?r permettra de matcher à la fois l’écriture américaine et anglaise

re.findall("colou?r", "Did you write color or colour?")
['color', 'colour']

Ces quantifiers peuvent bien sûr être associés à d’autres types de caractères, notamment les classes de caractères. Cela peut être extrèmement pratique. Par exemple, \d+ permettra de capturer un ou plusieurs chiffres, \s? permettra d’ajouter en option un espace, [\w]{6,8} un mot entre six et huit lettres qu’on écrira…

Il est aussi possible de définir le nombre de répétitions avec {}:

  • {n} matche exactement n fois ;
  • {n,} matche au moins n fois ;
  • {n,m} matche entre n et m fois.

Cependant, la répétition des termes ne s’applique par défaut qu’au dernier caractère précédent le quantifier. On peut s’en convaincre avec l’exemple ci-dessus:

print(re.match("toc{4}", "toctoctoctoc"))
None

Pour pallier ce problème, il existe les parenthèses. Le principe est le même qu’avec les règles numériques: les parenthèses permettent d’introduire une hiérarchie. Pour reprendre l’exemple précédent, on obtient bien le résultat attendu grâce aux parenthèses:

print(re.match("(toc){4}", "toctoctoctoc"))
print(re.match("(toc){5}", "toctoctoctoc"))
print(re.match("(toc){2,4}", "toctoctoctoc"))
<re.Match object; span=(0, 12), match='toctoctoctoc'>
None
<re.Match object; span=(0, 12), match='toctoctoctoc'>
Note

L’algorithme des expressions régulières essaye toujours de faire correspondre le plus grand morceau à l’expression régulière.

Par exemple, soit une chaine de caractère HTML:

s = "<h1>Super titre HTML</h1>"

L’expression régulière re.findall("<.*>", s) correspond, potentiellement, à trois morceaux :

  • <h1>
  • </h1>
  • <h1>Super titre HTML</h1>

C’est ce dernier qui sera choisi, car le plus grand. Pour sélectionner le plus petit, il faudra écrire les multiplicateurs comme ceci : *?, +?. En voici quelques exemples:

s = "<h1>Super titre HTML</h1>\n<p><code>Python</code> est un langage très flexible</p>"
print(re.findall("<.*>", s))
print(re.findall("<p>.*</p>", s))
print(re.findall("<p>.*?</p>", s))
print(re.compile("<.*?>").findall(s))
['<h1>Super titre HTML</h1>', '<p><code>Python</code> est un langage très flexible</p>']
['<p><code>Python</code> est un langage très flexible</p>']
['<p><code>Python</code> est un langage très flexible</p>']
['<h1>', '</h1>', '<p>', '<code>', '</code>', '</p>']

2.3 Aide-mémoire

Le tableau ci-dessous peut servir d’aide-mémoire sur les regex:

Expression régulière Signification
"^" Début de la chaîne de caractères
"$" Fin de la chaîne de caractères
"\\." Un point
"." N’importe quel caractère
".+" N’importe quelle suite de caractères non vide
".*" N’importe quelle suite de caractères, éventuellement vi
"[:alnum:]" Un caractère alphanumérique
"[:alpha:]" Une lettre
"[:digit:]" Un chiffre
"[:lower:]" Une lettre minuscule
"[:punct:]" Un signe de ponctuation
"[:space:]" un espace
"[:upper:]" Une lettre majuscule
"[[:alnum:]]+" Une suite d’au moins un caractère alphanumérique
"[[:alpha:]]+" Une suite d’au moins une lettre
"[[:digit:]]+" Une suite d’au moins un chiffre
"[[:lower:]]+" Une suite d’au moins une lettre minuscule
"[[:punct:]]+" Une suite d’au moins un signe de ponctuation
"[[:space:]]+" Une suite d’au moins un espace
"[[:upper:]]+" Une suite d’au moins une lettre majuscule
"[[:alnum:]]*" Une suite de caractères alphanumériques, éventuellement vide
"[[:alpha:]]*" Une suite de lettres, éventuellement vide
"[[:digit:]]*" Une suite de chiffres, éventuellement vide
"[[:lower:]]*" Une suite de lettres minuscules, éventuellement vide
"[[:upper:]]*" Une suite de lettres majuscules, éventuellement vide
"[[:punct:]]*" Une suite de signes de ponctuation, éventuellement vide
"[^[:alpha:]]+" Une suite d’au moins un caractère autre qu’une lettre
"[^[:digit:]]+" Une suite d’au moins un caractère autre qu’un chiffre
"\|" L’une des expressions x ou y est présente
[abyz] Un seul des caractères spécifiés
[abyz]+ Un ou plusieurs des caractères spécifiés (éventuellement répétés)
[^abyz] Aucun des caractères spécifiés n’est présent

Certaines classes de caractères bénéficient d’une syntaxe plus légère car elles sont très fréquentes. Parmi-celles:

Expression régulière Signification
\d N’importe quel chiffre
\D N’importe quel caractère qui n’est pas un caractère
\s N’importe quel espace (espace, tabulation, retour à la ligne)
\S N’importe quel caractère qui n’est pas un espace
\w N’importe quel type de mot (lettres et nombres)
\W N’importe quel ensemble qui n’est pas un mot (lettres et nombres)

Dans l’exercice suivant, vous allez pouvoir mettre en pratique les exemples précédents sur une regex un peu plus complète. Cet exercice ne nécessite pas la connaissance des subtilités du package re, vous n’aurez besoin que de re.findall.

Cet exercice utilisera la chaine de caractère suivante :

s = """date 0 : 14/9/2000
date 1 : 20/04/1971     date 2 : 14/09/1913     date 3 : 2/3/1978
date 4 : 1/7/1986     date 5 : 7/3/47     date 6 : 15/10/1914
date 7 : 08/03/1941     date 8 : 8/1/1980     date 9 : 30/6/1976"""
s
'date 0 : 14/9/2000\ndate 1 : 20/04/1971     date 2 : 14/09/1913     date 3 : 2/3/1978\ndate 4 : 1/7/1986     date 5 : 7/3/47     date 6 : 15/10/1914\ndate 7 : 08/03/1941     date 8 : 8/1/1980     date 9 : 30/6/1976'
Exercice 1
  1. On va d’abord s’occuper d’extraire le jour de naissance.
    • Le premier chiffre du jour est 0, 1, 2 ou 3. Traduire cela sous la forme d’une séquence [X-X]
    • Le deuxième chiffre du jour est lui entre 0 et 9. Traduire cela sous la séquence adéquate
    • Remarquez que le premier jour est facultatif. Intercaler entre les deux classes de caractère adéquate le quantifieur qui convient
    • Ajouter le slash à la suite du motif
    • Tester avec re.findall. Vous devriez obtenir beaucoup plus d’échos que nécessaire. C’est normal, à ce stade la regex n’est pas encore finalisée
  2. Suivre la même logique pour les mois en notant que les mois du calendrier grégorien ne dépassent jamais la première dizaine. Tester avec re.findall
  3. De même pour les années de naissance en notant que jusqu’à preuve du contraire, pour des personnes vivantes aujourd’hui, les millénaires concernés sont restreints. Tester avec re.findall
  4. Cette regex n’est pas naturelle, on pourrait très bien se satisfaire de classes de caractères génériques \d même si elles pourraient, en pratique, nous sélectionner des dates de naissance non possibles (43/78/4528 par exemple). Cela permettrait d’alléger la regex afin de la rendre plus intelligible. Ne pas oublier l’utilité des quantifieurs.
  5. Comment adapter la regex pour qu’elle soit toujours valide pour nos cas mais permette aussi de capturer les dates de type YYYY/MM/DD ? Tester sur 1998/07/12

A l’issue de la question 1, vous devriez avoir ce résultat :

['14/',
 '9/',
 '20/',
 '04/',
 '14/',
 '09/',
 '2/',
 '3/',
 '1/',
 '7/',
 '7/',
 '3/',
 '15/',
 '10/',
 '08/',
 '03/',
 '8/',
 '1/',
 '30/',
 '6/']

A l’issue de la question 2, vous devriez avoir ce résultat, qui commence à prendre forme:

['14/9',
 '20/04',
 '14/09',
 '2/3',
 '1/7',
 '7/3',
 '15/10',
 '08/03',
 '8/1',
 '30/6']

A l’issue de la question 3, on parvient bien à extraire les dates :

['14/9/2000',
 '20/04/1971',
 '14/09/1913',
 '2/3/1978',
 '1/7/1986',
 '7/3/47',
 '15/10/1914',
 '08/03/1941',
 '8/1/1980',
 '30/6/1976']

Si tout va bien, à la question 5, votre regex devrait fonctionner:

['14/9/2000',
 '20/04/1971',
 '14/09/1913',
 '2/3/1978',
 '1/7/1986',
 '7/3/47',
 '15/10/1914',
 '08/03/1941',
 '8/1/1980',
 '30/6/1976',
 '1998/07/12']

3 Principales fonctions de re

Voici un tableau récapitulatif des principales fonctions du package re suivi d’exemples.

Nous avons principalement utilisé jusqu’à présent re.findall qui est l’une des fonctions les plus pratiques du package. re.sub et re.search sont également bien pratiques. Les autres sont moins vitales mais peuvent dans des cas précis être utiles.

Fonction Objectif
re.match(<regex>, s) Trouver et renvoyer le premier match de l’expression régulière <regex> à partir du début du string s
re.search(<regex>, s) Trouver et renvoyer le premier match de l’expression régulière <regex> quelle que soit sa position dans le string s
re.finditer(<regex>, s) Trouver et renvoyer un itérateur stockant tous les matches de l’expression régulière <regex> quelle que soit leur(s) position(s) dans le string s. En général, on effectue ensuite une boucle sur cet itérateur
re.findall(<regex>, s) Trouver et renvoyer tous les matches de l’expression régulière <regex> quelle que soit leur(s) position(s) dans le string s sous forme de liste
re.sub(<regex>, new_text, s) Trouver et remplacer tous les matches de l’expression régulière <regex> quelle que soit leur(s) position(s) dans le string s

Pour illustrer ces fonctions, voici quelques exemples:

Exemple de re.match 👇

re.match ne peut servir qu’à capturer un pattern en début de string. Son utilité est donc limitée. Capturons néanmoins toto :

re.match("(to){2}", "toto à la plage")
<re.Match object; span=(0, 4), match='toto'>
Exemple de re.search 👇

re.search est plus puissant que re.match, on peut capturer des termes quelle que soit leur position dans un string. Par exemple, pour capturer age :

re.search("age", "toto a l'age d'aller à la plage")
<re.Match object; span=(9, 12), match='age'>

Et pour capturer exclusivement “age” en fin de string :

re.search("age$", "toto a l'age d'aller à la plage")
<re.Match object; span=(28, 31), match='age'>
Exemple de re.finditer 👇

re.finditer est, à mon avis, moins pratique que re.findall. Son utilité principale par rapport à re.findall est de capturer la position dans un champ textuel:

s = "toto a l'age d'aller à la plage"
for match in re.finditer("age", s):
    start = match.start()
    end = match.end()
    print(f'String match "{s[start:end]}" at {start}:{end}')
String match "age" at 9:12
String match "age" at 28:31
Exemple de re.sub 👇

re.sub permet de capturer et remplacer des expressions. Par exemple, remplaçons “age” par “âge”. Mais attention, il ne faut pas le faire lorsque le motif est présent dans “plage”. On va donc mettre une condition négative: capturer “age” seulement s’il n’est pas en fin de string (ce qui se traduit en regex par ?!$)

re.sub("age(?!$)", "âge", "toto a l'age d'aller à la plage")
"toto a l'âge d'aller à la plage"
Quand utiliser re.compile et les raw strings ?

re.compile peut être intéressant lorsque vous utilisez une expression régulière plusieurs fois dans votre code. Cela permet de compiler l’expression régulière en un objet reconnu par re, ce qui peut être plus efficace en termes de performance lorsque l’expression régulière est utilisée à plusieurs reprises ou sur des données volumineuses.

Les chaînes brutes (raw string) sont des chaînes de caractères spéciales en Python, qui commencent par r. Par exemple r"toto à la plage". Elles peuvent être intéressantes pour éviter que les caractères d’échappement ne soient interprétés par Python Par exemple, si vous voulez chercher une chaîne qui contient une barre oblique inverse \ dans une chaîne, vous devez utiliser une chaîne brute pour éviter que la barre oblique inverse ne soit interprétée comme un caractère d’échappement (\t, \n, etc.). Le testeur https://regex101.com/ suppose d’ailleurs que vous utilisez des raw string, cela peut donc être utile de s’habituer à les utiliser.

4 Généralisation avec Pandas

Les méthodes de Pandas sont des extensions de celles de re qui évitent de faire une boucle pour regarder, ligne à ligne, une regex. En pratique, lorsqu’on traite des DataFrames, on utilise plutôt l’API Pandas que re. Les codes de la forme df.apply(lambda x: re.<fonction>(<regex>,x), axis = 1) sont à bannir car très peu efficaces.

Les noms changent parfois légèrement par rapport à leur équivalent re.

Méthode Description
str.count() Compter le nombre d’occurrences du pattern dans chaque ligne
str.replace() Remplacer le pattern par une autre valeur. Version vectorisée de re.sub()
str.contains() Tester si le pattern apparaît, ligne à ligne. Version vectorisée de re.search()
str.extract() Extraire les groupes qui répondent à un pattern et les renvoyer dans une colonne
str.findall() Trouver et renvoyer toutes les occurrences d’un pattern. Si une ligne comporte plusieurs échos, une liste est renvoyée. Version vectorisée de re.findall()

A ces fonctions, s’ajoutent les méthodes str.split() et str.rsplit() qui sont bien pratiques.

Exemple de str.count 👇

On peut compter le nombre de fois qu’un pattern apparaît avec str.count

df = pd.DataFrame({"a": ["toto", "titi"]})
df["a"].str.count("to")
0    2
1    0
Name: a, dtype: int64
Exemple de str.replace 👇

Remplaçons le motif “ti” en fin de phrase

df = pd.DataFrame({"a": ["toto", "titi"]})
df["a"].str.replace("ti$", " punch")
0    toto
1    titi
Name: a, dtype: object
Exemple de str.contains 👇

Vérifions les cas où notre ligne termine par “ti” :

df = pd.DataFrame({"a": ["toto", "titi"]})
df["a"].str.contains("ti$")
0    False
1     True
Name: a, dtype: bool
Exemple de str.findall 👇
df = pd.DataFrame({"a": ["toto", "titi"]})
df["a"].str.findall("to")
0    [to, to]
1          []
Name: a, dtype: object
Warning

A l’heure actuelle, il n’est pas nécessaire d’ajouter l’argument regex = True mais cela devrait être le cas dans une future version de Pandas. Cela peut valoir le coup de s’habituer à l’ajouter.

5 Pour en savoir plus

6 Exercices supplémentaires

6.1 Extraction d’adresses email

Il s’agit d’un usage classique des regex

text_emails = (
    "Hello from toto@gmail.com to titi.grominet@yahoo.com about the meeting @2PM"
)
Exercice : extraction d’adresses email

Utiliser la structure d’une adresse mail [XXXX]@[XXXX] pour récupérer ce contenu

['toto@gmail.com', 'titi.grominet@yahoo.com']

6.2 Extraire des années depuis un DataFrame Pandas

L’objectif général de l’exercice est de nettoyer des colonnes d’un DataFrame en utilisant des expressions régulières.

Exercice

La base en question contient des livres de la British Library et quelques informations les concernant. Le jeu de données est disponible ici : https://raw.githubusercontent.com/realpython/python-data-cleaning/master/Datasets/BL-Flickr-Images-Book.csv

La colonne “Date de Publication” n’est pas toujours une année, il y a parfois d’autres informations. Le but de l’exercice est d’avoir une date de publication du livre propre et de regarder la distribution des années de publications.

Pour ce faire, vous pouvez :

  • Soit choisir de réaliser l’exercice sans aide. Votre lecture de l’énoncé s’arrête donc ici. Vous devez alors faire attention à bien regarder vous-même la base de données et la transformer avec attention.

  • Soit suivre les différentes étapes qui suivent pas à pas.

Version guidée 👇
  1. Lire les données depuis l’url https://raw.githubusercontent.com/realpython/python-data-cleaning/master/Datasets/BL-Flickr-Images-Book.csv. Attention au séparateur
  2. Ne garder que les colonnes ['Identifier', 'Place of Publication', 'Date of Publication', 'Publisher', 'Title', 'Author']
  3. Observer la colonne ‘Date of Publication’ et remarquer le problème sur certaines lignes (par exemple la ligne 13)
  4. Commencez par regarder le nombre d’informations manquantes. On ne pourra pas avoir mieux après la regex, et normalement on ne devrait pas avoir moins…
  5. Déterminer la forme de la regex pour une date de publication. A priori, il y a 4 chiffres qui forment une année. Utiliser la méthode str.extract() avec l’argument expand = False (pour ne conserver que la première date concordant avec notre pattern)?
  6. On a 2 NaN qui n’étaient pas présents au début de l’exercice. Quels sont-ils et pourquoi ?
  7. Quelle est la répartition des dates de publications dans le jeu de données ? Vous pouvez par exemple afficher un histogramme grâce à la méthode plot avec l’argument kind ="hist".

Voici par exemple le problème qu’on demande de détecter à la question 3 :

Date of Publication Title
13 1839, 38-54 De Aardbol. Magazijn van hedendaagsche land- e...
14 1897 Cronache Savonesi dal 1500 al 1570 ... Accresc...
15 1865 See-Saw; a novel ... Edited [or rather, writte...
16 1860-63 Géodésie d'une partie de la Haute Éthiopie,...
17 1873 [With eleven maps.]
18 1866 [Historia geográfica, civil y politica de la ...
19 1899 The Crisis of the Revolution, being the story ...
181

Grâce à notre regex (question 5), on obtient ainsi un DataFrame plus conforme à nos attentes

Date of Publication year
0 1879 [1878] 1879
7 NaN NaN
13 1839, 38-54 1839
16 1860-63 1860
23 1847, 48 [1846-48] 1847
... ... ...
8278 1883, [1884] 1883
8279 1898-1912 1898
8283 1831, 32 1831
8284 [1806]-22 1806
8286 1834-43 1834

1759 rows × 2 columns

Quant aux nouveaux NaN, il s’agit de lignes qui ne contenaient pas de chaînes de caractères qui ressemblaient à des années :

Date of Publication year
1081 112. G. & W. B. Whittaker NaN
7391 17 vols. University Press NaN

Enfin, on obtient l’histogramme suivant des dates de publications:

Informations additionnelles

environment files have been tested on.

Latest built version: 2024-07-26

Python version used:

'3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:40:35) [GCC 12.3.0]'
Package Version
affine 2.4.0
aiobotocore 2.12.2
aiohttp 3.9.3
aioitertools 0.11.0
aiosignal 1.3.1
alembic 1.13.1
aniso8601 9.0.1
annotated-types 0.7.0
appdirs 1.4.4
archspec 0.2.3
astroid 3.1.0
asttokens 2.4.1
attrs 23.2.0
Babel 2.15.0
bcrypt 4.1.2
beautifulsoup4 4.12.3
black 24.4.2
blinker 1.7.0
blis 0.7.11
bokeh 3.4.0
boltons 23.1.1
boto3 1.34.51
botocore 1.34.51
branca 0.7.1
Brotli 1.1.0
cachetools 5.3.3
cartiflette 0.0.2
Cartopy 0.23.0
catalogue 2.0.10
cattrs 23.2.3
certifi 2024.2.2
cffi 1.16.0
charset-normalizer 3.3.2
chromedriver-autoinstaller 0.6.4
click 8.1.7
click-plugins 1.1.1
cligj 0.7.2
cloudpathlib 0.18.1
cloudpickle 3.0.0
colorama 0.4.6
comm 0.2.2
commonmark 0.9.1
conda 24.3.0
conda-libmamba-solver 24.1.0
conda-package-handling 2.2.0
conda_package_streaming 0.9.0
confection 0.1.5
contextily 1.6.0
contourpy 1.2.1
cryptography 42.0.5
cycler 0.12.1
cymem 2.0.8
cytoolz 0.12.3
dask 2024.4.1
dask-expr 1.0.10
debugpy 1.8.1
decorator 5.1.1
dill 0.3.8
distributed 2024.4.1
distro 1.9.0
docker 7.0.0
duckdb 0.10.1
en-core-web-sm 3.7.1
entrypoints 0.4
et-xmlfile 1.1.0
exceptiongroup 1.2.0
executing 2.0.1
fastjsonschema 2.19.1
fiona 1.9.6
flake8 7.0.0
Flask 3.0.2
folium 0.16.0
fontawesomefree 6.6.0
fonttools 4.51.0
frozenlist 1.4.1
fsspec 2023.12.2
GDAL 3.8.4
gensim 4.3.2
geographiclib 2.0
geopandas 0.12.2
geoplot 0.5.1
geopy 2.4.1
gitdb 4.0.11
GitPython 3.1.43
google-auth 2.29.0
graphene 3.3
graphql-core 3.2.3
graphql-relay 3.2.0
graphviz 0.20.3
great-tables 0.10.0
greenlet 3.0.3
gunicorn 21.2.0
h11 0.14.0
htmltools 0.5.3
hvac 2.1.0
idna 3.6
imageio 2.34.2
importlib_metadata 7.1.0
importlib_resources 6.4.0
inflate64 1.0.0
ipykernel 6.29.3
ipython 8.22.2
ipywidgets 8.1.2
isort 5.13.2
itsdangerous 2.1.2
jedi 0.19.1
Jinja2 3.1.3
jmespath 1.0.1
joblib 1.3.2
jsonpatch 1.33
jsonpointer 2.4
jsonschema 4.21.1
jsonschema-specifications 2023.12.1
jupyter-cache 1.0.0
jupyter_client 8.6.1
jupyter_core 5.7.2
jupyterlab_widgets 3.0.10
kaleido 0.2.1
kiwisolver 1.4.5
kubernetes 29.0.0
langcodes 3.4.0
language_data 1.2.0
lazy_loader 0.4
libmambapy 1.5.7
llvmlite 0.42.0
locket 1.0.0
lxml 5.2.2
lz4 4.3.3
Mako 1.3.2
mamba 1.5.7
mapclassify 2.6.1
marisa-trie 1.2.0
Markdown 3.6
markdown-it-py 3.0.0
MarkupSafe 2.1.5
matplotlib 3.8.3
matplotlib-inline 0.1.6
mccabe 0.7.0
mdurl 0.1.2
menuinst 2.0.2
mercantile 1.2.1
mizani 0.11.4
mlflow 2.11.3
mlflow-skinny 2.11.3
msgpack 1.0.7
multidict 6.0.5
multivolumefile 0.2.3
munkres 1.1.4
murmurhash 1.0.10
mypy 1.9.0
mypy-extensions 1.0.0
nbclient 0.10.0
nbformat 5.10.4
nest_asyncio 1.6.0
networkx 3.3
nltk 3.8.1
numba 0.59.1
numpy 1.26.4
oauthlib 3.2.2
opencv-python-headless 4.9.0.80
openpyxl 3.1.5
outcome 1.3.0.post0
OWSLib 0.28.1
packaging 23.2
pandas 2.2.1
paramiko 3.4.0
parso 0.8.4
partd 1.4.1
pathspec 0.12.1
patsy 0.5.6
Pebble 5.0.7
pexpect 4.9.0
pickleshare 0.7.5
pillow 10.3.0
pip 24.0
pkgutil_resolve_name 1.3.10
platformdirs 4.2.0
plotly 5.19.0
plotnine 0.13.6
pluggy 1.4.0
polars 0.20.31
preshed 3.0.9
prometheus_client 0.20.0
prometheus-flask-exporter 0.23.0
prompt-toolkit 3.0.42
protobuf 4.25.3
psutil 5.9.8
ptyprocess 0.7.0
pure-eval 0.2.2
py7zr 0.20.8
pyarrow 15.0.0
pyarrow-hotfix 0.6
pyasn1 0.5.1
pyasn1-modules 0.3.0
pybcj 1.0.2
pycodestyle 2.11.1
pycosat 0.6.6
pycparser 2.21
pycryptodomex 3.20.0
pydantic 2.8.2
pydantic_core 2.20.1
pyflakes 3.2.0
Pygments 2.17.2
PyJWT 2.8.0
pylint 3.1.0
PyNaCl 1.5.0
pynsee 0.1.7
pyOpenSSL 24.0.0
pyparsing 3.1.2
pyppmd 1.1.0
pyproj 3.6.1
pyshp 2.3.1
PySocks 1.7.1
python-dateutil 2.9.0
python-dotenv 1.0.1
python-magic 0.4.27
pytz 2024.1
pyu2f 0.1.5
pywaffle 1.1.1
PyYAML 6.0.1
pyzmq 25.1.2
pyzstd 0.16.0
QtPy 2.4.1
querystring-parser 1.2.4
rasterio 1.3.10
referencing 0.34.0
regex 2023.12.25
requests 2.31.0
requests-cache 1.2.1
requests-oauthlib 2.0.0
rich 13.7.1
rpds-py 0.18.0
rsa 4.9
Rtree 1.2.0
ruamel.yaml 0.18.6
ruamel.yaml.clib 0.2.8
s3fs 2023.12.2
s3transfer 0.10.1
scikit-image 0.24.0
scikit-learn 1.4.1.post1
scipy 1.13.0
seaborn 0.13.2
selenium 4.23.1
setuptools 69.2.0
shapely 2.0.3
shellingham 1.5.4
six 1.16.0
smart_open 7.0.4
smmap 5.0.0
sniffio 1.3.1
snuggs 1.4.7
sortedcontainers 2.4.0
soupsieve 2.5
spacy 3.7.5
spacy-legacy 3.0.12
spacy-loggers 1.0.5
SQLAlchemy 2.0.29
sqlparse 0.4.4
srsly 2.4.8
stack-data 0.6.2
statsmodels 0.14.1
tabulate 0.9.0
tblib 3.0.0
tenacity 8.2.3
texttable 1.7.0
thinc 8.2.5
threadpoolctl 3.4.0
tifffile 2024.7.24
tomli 2.0.1
tomlkit 0.12.4
toolz 0.12.1
topojson 1.9
tornado 6.4
tqdm 4.66.2
traitlets 5.14.2
trio 0.26.0
trio-websocket 0.11.1
truststore 0.8.0
typer 0.12.3
typing_extensions 4.11.0
tzdata 2024.1
Unidecode 1.3.8
url-normalize 1.4.3
urllib3 1.26.18
wasabi 1.1.3
wcwidth 0.2.13
weasel 0.4.1
webdriver-manager 4.0.2
websocket-client 1.8.0
Werkzeug 3.0.2
wheel 0.43.0
widgetsnbextension 4.0.10
wordcloud 1.9.3
wrapt 1.16.0
wsproto 1.2.0
xgboost 2.0.3
xlrd 2.0.1
xyzservices 2024.4.0
yarl 1.9.4
yellowbrick 1.5
zict 3.0.0
zipp 3.17.0
zstandard 0.22.0

View file history

SHA Date Author Description
065b0ab 2024-07-08 11:19:43 Lino Galiana Nouveaux callout dans la partie manipulation (#513)
06d003a 2024-04-23 10:09:22 Lino Galiana Continue la restructuration des sous-parties (#492)
005d89b 2023-12-20 17:23:04 Lino Galiana Finalise l’affichage des statistiques Git (#478)
3fba612 2023-12-17 18:16:42 Lino Galiana Remove some badges from python (#476)
a06a268 2023-11-23 18:23:28 Antoine Palazzolo 2ème relectures chapitres ML (#457)
69cf52b 2023-11-21 16:12:37 Antoine Palazzolo [On-going] Suggestions chapitres modélisation (#452)
889a71b 2023-11-10 11:40:51 Antoine Palazzolo Modification TP 3 (#443)
a771183 2023-10-09 11:27:45 Antoine Palazzolo Relecture TD2 par Antoine (#418)
154f09e 2023-09-26 14:59:11 Antoine Palazzolo Des typos corrigées par Antoine (#411)
9a4e226 2023-08-28 17:11:52 Lino Galiana Action to check URL still exist (#399)
3bdf3b0 2023-08-25 11:23:02 Lino Galiana Simplification de la structure 🤓 (#393)
130ed71 2023-07-18 19:37:11 Lino Galiana Restructure les titres (#374)
f0c583c 2023-07-07 14:12:22 Lino Galiana Images viz (#371)
ef28fef 2023-07-07 08:14:42 Lino Galiana Listing pour la première partie (#369)
f21a24d 2023-07-02 10:58:15 Lino Galiana Pipeline Quarto & Pages 🚀 (#365)
62b2a7c 2022-12-28 15:00:50 Lino Galiana Suite chapitre regex (#340)
3c880d5 2022-12-27 17:34:59 Lino Galiana Chapitre regex + Change les boites dans plusieurs chapitres (#339)
f10815b 2022-08-25 16:00:03 Lino Galiana Notebooks should now look more beautiful (#260)
494a85a 2022-08-05 14:49:56 Lino Galiana Images featured ✨ (#252)
d201e3c 2022-08-03 15:50:34 Lino Galiana Pimp la homepage ✨ (#249)
12965ba 2022-05-25 15:53:27 Lino Galiana :launch: Bascule vers quarto (#226)
9c71d6e 2022-03-08 10:34:26 Lino Galiana Plus d’éléments sur S3 (#218)
9a3f7ad 2021-10-31 18:36:25 Lino Galiana Nettoyage partie API + Git (#170)
2a8809f 2021-10-27 12:05:34 Lino Galiana Simplification des hooks pour gagner en flexibilité et clarté (#166)
b138cf3 2021-10-21 18:05:59 Lino Galiana Mise à jour TP webscraping et API (#164)
2e4d586 2021-09-02 12:03:39 Lino Galiana Simplify badges generation (#130)
a5f4824 2021-07-16 14:20:27 Lino Galiana Exo supplémentaire webscraping marmiton 🍝 (#121) (#124)
4cdb759 2021-05-12 10:37:23 Lino Galiana :sparkles: :star2: Nouveau thème hugo :snake: :fire: (#105)
6d010fa 2020-09-29 18:45:34 Lino Galiana Simplifie l’arborescence du site, partie 1 (#57)
66f9f87 2020-09-24 19:23:04 Lino Galiana Introduction des figures générées par python dans le site (#52)
5c1e76d 2020-09-09 11:25:38 Lino Galiana Ajout des éléments webscraping, regex, API (#21)
Back to top

Footnotes

  1. N’importe quel caractère à part le retour à la ligne (\n). Ceci est à garder en tête, j’ai déjà perdu des heures à chercher pourquoi mon . ne capturait pas ce que je voulais qui s’étalait sur plusieurs lignes…↩︎

Citation

BibTeX citation:
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python Pour La Data Science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {en}
}
For attribution, please cite this work as:
Galiana, Lino. 2023. Python Pour La Data Science. https://doi.org/10.5281/zenodo.8229676.