Les expressions régulières : le super rechercher-remplacer

Les expressions régulières sont un outil extrêmement utile pour réaliser des opérations complexes de recherche-remplacer sur des grandes quantités de texte, qui nous semble d’intérêt général pour tous les chercheurs en sciences sociales. On en présente ici le principe, la syntaxe et comment les utiliser concrètement.

Un outil indispensable pour les travailleurs du texte

Nous passons notre temps, en sciences sociales, à manipuler des données textuelles : réponses ouvertes à un questionnaire, noms de personnes, lieux, caractéristiques sociales, mais aussi articles de presse, correspondances, transcriptions d’entretiens, jusqu’aux textes scientifiques que nous produisons. Or, pour traiter de telles données avec un ordinateur, il est fréquent de devoir effectuer des transformations systématiques, à des fins qui vont d’un nettoyage avant importation pour une analyse statistique, jusqu’à une conformation à des normes typographiques avant publication.

Pourquoi programmer pour écrire ?

Parmi les outils que nous utilisons couramment, certains permettent d’effectuer de telles modifications, en particulier R et OpenOffice/LibreOffice ou Word. Chacun a déjà utilisé les fonctions de recherche et de “rechercher-remplacer” de Word ou équivalent. On présente ici une généralisation de ces fonctions qui permet d’automatiser rapidement presque toutes les opérations textuelles imaginables. Pour les utiliser, il suffit d’un langage de programmation (par exemple R, Python, etc.) ou d’un bon éditeur de texte. Parmi les logiciels de traitement de texte, LibreOffice et OpenOffice permettent de faire la même chose ; c’est le cas aussi de Word (mais avec une syntaxe assez différente, qu’on n’évoquera pas ici).

Nous avons donc sous la main des logiciels dotés de fonctions à même de nous épargner des heures de travail fastidieux, mais bien souvent nous ne savons pas les utiliser, ou nous ne savons même pas qu’elles existent.

Le sujet peut paraître compliqué au premier abord, et si vous regardez rapidement la suite de ce billet, vous risquez de ne voir que des signes cabalistiques rebutants. Pas d’inquiétude toutefois : les expressions régulières ne sont pas faites pour être lues, mais pour être faciles à écrire.

Ce billet propose une introduction à ce sujet, afin de montrer la logique de cet outil, à la fois très simple et puissant. Car le plus important n’est pas de connaitre les expressions régulières, mais d’en comprendre le principe et savoir ce qu’il est possible de faire avec. Après quoi, il existe de nombreuses aides que même les chercheurs expérimentés consultent souvent.

Le principe : faire repérer des formes textuelles à l’ordinateur

Supposons que, pour les individus d’une population dont on veut faire une prosopographie, on ait saisi une liste de dates et lieux de naissance dans un fichier texte (ou un vecteur de type character sous R) – ici des membres des Francs Tireurs et partisans (FTP) :

né le 3 juillet 1897, à Rennes (35)
né le 13 mai 1893, à Le Chambon-Feugerolles (42)
née en 1895, à Tauriac (47)
né le 2 octobre 1896, à Louey (65)
née le 1er janvier 1908, à Końskowola -- Pologne (99)
…

Comment transformer cette série de lignes de texte, dont la structure est toujours la même, en une autre série de lignes de texte, chacune comportant uniquement l’année de naissance ? Si on sait réaliser cette opération, alors on pourra faire de même pour le département et les autres données qui nous intéressent, et transformer ce texte en base de données prête pour l’analyse. Concentrons-nous donc sur l’année.

À conceptualiser, l’opération est très simple. Il suffit, dans chaque ligne, de rechercher l’année, de la mémoriser, puis de remplacer le contenu de la ligne par elle. Comment repérer l’année ? À l’œil nu, rien de plus facile : c’est le nombre composé de quatre chiffres. On pourrait aussi remarquer qu’elle ici est le deuxième nombre de chaque ligne, ou encore le premier nombre qui soit suivi immédiatement d’une virgule, etc. Toutes ces désignations de formes textuelles sont ce qu’on appelle des motifs (patterns, en anglais).

Si on se décide pour cette première caractérisation de l’année, la procédure qu’on souhaitera effectuer pourra alors s’écrire, en langage humain :

Dans chaque ligne,
  Remplace: l'ensemble de la ligne
  Par: le premier nombre à quatre chiffres trouvé

Le travail est donc très simple à penser. Mais s’il doit être répété sur un grand nombre de lignes, c’est fastidieux. Certains vont donc employer un esclave doctorant, mais un programme informatique de quelques lignes le fait aussi bien. Et le travail de programmation qui peut paraître compliqué au premier abord est en réalité très simple à écrire, au point que c’est une solution plus rapide (et même plus sûre) que de recopier manuellement les informations qui vous intéressent, et ce dès que vous avez plus que quelques lignes[1].

En effet, la moitié du travail, à savoir l’écriture de la procédure “comme si on faisait à la main” est déjà réalisée. Il ne reste plus qu’à savoir comment la faire comprendre à l’ordinateur, et comment la lui faire exécuter.

La syntaxe qui permet de traduire les instructions précédentes pour l’ordinateur, d’indiquer les motifs qu’on désire repérer, est appelée expressions régulières, en anglais regular expressions, souvent abbrevié en regex[2]. Il en existe plusieurs variantes. R utilise une syntaxe très puissante et très répandue, appelée PCRE ou parfois “de type perl”[3], comme à peu près tous les langages de programmation généralistes.

Bases de la syntaxe

Les expressions régulières sont donc une syntaxe pour décrire de manière assez naturelle des formes textuelles à un ordinateur. Cela n’a rien d’évident a priori, mais on va voir comment désigner des fragments de formes comme “n’importe quoi”, “quatre chiffres exactement”, “au moins une majuscule”, etc. La syntaxe comportera, inévitablement, des symboles un peu cabalistiques, mais il ceux-ci ne sont pas nombreux, et l’important est qu’ils se combinent comme du Lego.

Classes de caractères et quantificateurs

La syntaxe des expressions régulières se compose de deux types d’éléments : des groupes de caractères (par exemple : les chiffres, les lettres, les espaces, mais aussi des groupes que l’on peut définir soi-même, comme les lettres a et c, les chiffres de 0 à 7…)[4], et des quantificateurs (par exemple : un, deux, zéro ou un, un ou plus, entre 4 et 7, un nombre quelconque, etc.). On utilise ici cette convention graphique : gras pour les groupes de caractères, italique pour les quantificateurs.

Une expression régulière est ainsi une suite de groupe de caractères, chacun accompagné d’un quantificateur. Par exemple : un certain nombre de lettres, au moins un espace, exactement quatre chiffres.

Les groupes de caractères les plus courants sont les suivants (en PCRE) :

  • \w : les lettres (w pour word)
  • \d : les chiffres (d pour digit)
  • \s : les espaces (s pour spaces)
  • [A-Z] : les majuscules
  • [abd;_] : les lettres a, b, et d, le point-virgule (;), et l’underscore (_)
  • . : n’importe quel caractère (excepté le saut de ligne)
  • k : la lettre k
  • (k|\d{2}) : la lettre k, ou bien deux chiffres

Il existe également leur négation :

  • \W : tout sauf les lettres
  • \D : tout sauf les chiffres
  • \S : tout sauf les espace
  • [^A-Z] : tout sauf les majuscules
  • [^abd;_] : tout sauf a, b, d, le point-virgule (;) ou l’underscore (_)

Maintenant les quantificateurs :

  • * : zéro ou plus
  • + : un ou plus
  • ? : zéro ou un
  • {2} : deux exactement
  • {2,5} : entre 2 et 5 inclus
  • {,6} : entre 0 et 6 inclus
  • {6,} : six ou plus
  • {2;5} : exactement deux ou cinq
  • (pas de quantificateur) : un exactement

Une absence de quantificateur désigne une et une seule occurrence. Ainsi, le motif abc correspond à un a, puis un b, puis un c, soit exactement le texte abc, comme d’habitude.

Avec ces bases, on peut déjà aller très loin. Revenons à l’exemple des FTP : on peut maintenant écrire le motif correspondant à quatre chiffres : \d{4} (ou \d\d\d\d, c’est la même chose).

Donc, si on veut extraire les années de l’exemple donné précedemment, on va chercher à écrire : “Dans chaque ligne, cherche un fragment de la forme \d{4}, et garde-le en mémoire”.

Mémoriser des fragments du texte analysé

Les expressions régulières servent à repérer des morceaux de texte correspondant à un motif que l’on écrit de façon formelle. Mais si on veut réutiliser ces morceaux de texte, il faut pouvoir les mettre en mémoire, et ensuite accéder au contenu de cette mémoire.

Pour cela, la syntaxe consiste à entourer ce qu’on veut mettre en mémoire de parenthèses. Ainsi, pour chercher et mettre en mémoire quatre chiffres, on écrira l’expression régulière (\d{4}).

Une fois appliqué cette expression régulière à une chaîne de caractères, le premier élément correspondant (ici le premier nombre à quatre chiffres trouvé) est stocké dans la variable \1 (ou parfois $1).

Ainsi, pour extraire l’année de né le 3 juillet 1897, à Rennes (35), on écrira : si tu trouves le motif (\d{4}) dans cette chaîne de caractères, renvoie-moi \1.

Caractères spéciaux

Vous aurez remarqué qu’on utilise certains caractères pour écrire les expressions régulières : \ { } ( ) [ ] . + ? - *, etc. Comment écrire pour dire qu’on veut identifier de tels caractères dans un texte, qui pourrait en contenir ?

Pour cela, on les fait simplement précéder de \, pour signaler qu’il ne faut pas les considérer comme caractères spéciaux, mais comme caractères réels. Ainsi, pour trouver un + dans un texte, on écrira dans l’expression régulière \+.

Il existe également d’autres caractères spéciaux, qu’on ne peut pas écrire normalement avec les touches du clavier. Il s’agit des espaces comme les sauts de ligne, de page, les tabulations ; ils s’écrivent comme suit, respectivement : \n, \f, \t.

Enfin, deux caractères spéciaux très commodes indique le début et la fin de la ligne : ^ et $, quand ils sont placés au début et à la fin de la regex. Ils s’écrivent aussi, respectivement, \A et \Z.

Ainsi, une ligne complète, contenant un nombre à quatre chiffres avec potentiellement des choses avant, et potentiellement des choses après, peut s’écrire ainsi :

^.*(\d{4}).*$

Pour la lire il faut détailler sa composition :

  • ^ : début du fragment de texte,
  • .* : n’importe quelle chaine de texte (peut contenir 0 caractère),
  • (\d{4}) : quatre chiffres, à garder en mémoire,
  • .* : n’importe quelle chaine de texte (peut contenir 0 caractère),
  • $ : fin du fragment de texte.

Il ne reste plus qu’à insérer ce motif dans un exemple d’utilisation concrète.

Utilisation concrète

Quels logiciels utiliser, pour faire des recherches ou du rechercher-remplacer ? On présente ici deux solutions possibles : R et LibreOffice. Chacune utilise une variante de la syntaxe présentée ici, mais les différences ne commencent à être visibles que dans les cas compliqués, donc on peut les négliger.

On va effectuer maintenant la même opération avec ces deux outils, sur l’exemple du début : sélectionner l’année dans une série de lignes de renseignements d’état-civil.

Les expressions régulières en R

La première chose à savoir, pour écrire des expressions régulières en R, est qu’il faut doubler tous les backslashes[5] : on n’écrit pas \d mais \\d.

Ensuite, admettons que nos lignes de texte soient représentées par un vecteur de type character. Il est alors facile d’effectuer pour l’ensemble des éléments de ce vecteur une opération de recherche des éléments qui correspondent à une regex, ou de recherche et remplacement sur ces éléments.

Fonctions de base de R : grep() et gsub()

La recherche utilise la fonction grep(regex, vecteur) – dont le nom signifie Globally search a Regular Expression and Print. Plus précisément, pour s’assurer qu’elle emploie la syntaxe qu’on a évoquée ici, il convient d’ajouter l’option perl = TRUE. Cela permet d’indiquer qu’on utilise la syntaxe PCRE. Cette fonction renvoie les numéros de position dans le vecteur recherché des éléments qui correspondent à la regex, ce qui est particulièrement utile pour sélectionner des lignes d’un dataframe (par exemple, df[grep(regex, df$var), ]). Elle peut aussi renvoyer les éléments eux-mêmes, avec l’option value = TRUE. Enfin la fonction grepl(regex, vecteur) – ne me demandez pas ce que signifie le l – renvoie TRUE ou FALSE pour chaque élément du vecteur, selon qu’il corresponde à la regex.

Les commandes suivantes les montrent en action :

> naiss <- c("né le 3 juillet 1897, à Rennes (35)",
             "né le 13 mai 1893, à Le Chambon-Feugerolles (42)",
             "née en 1895, à Tauriac (47)",
             "né le 2 octobre 1896, à Louey (65)",
             "née le 1er janvier 1908, à Końskowola -- Pologne (99)")
> grep("\\d{4}", naiss, perl = TRUE)
[1] 1 2 3 4 5
> grep("\\d{4}", naiss, perl = TRUE, value = TRUE)
[1] "né le 3 juillet 1897, à Rennes (35)"
[2] "né le 13 mai 1893, à Le Chambon-Feugerolles (42)"
[3] "née en 1895, à Tauriac (47)"
[4] "né le 2 octobre 1896, à Louey (65)"
[5] "née le 1er janvier 1908, à Końskowola - Pologne (99)"
> grep("\\d{6}", naiss, perl = TRUE)
integer(0)
> grepl("^née", naiss, perl = TRUE)
[1] FALSE FALSE  TRUE FALSE  TRUE

La première ligne crée un vecteur avec cinq arguments. La deuxième nous indique que la chaine de caractère demandée (4 chiffres à la suite) est présente dans les lignes 1, 2, 3, 4 et 5; et la troisième nous retourne les chaines en question grâce à l’option value = TRUE. La quatrième montre que la chaine de caractère 6 chiffres d’affilée n’est présente nulle part (integer(0)). Enfin, la dernière teste si elle commence ("^) par née et donc si la personne est une femme.

Le rechercher-remplacer s’effectue avec la fonction gsub(regex, remplacement, vecteur) – abréviation de Global SUBstitution. De la même manière, on conseille d’utiliser l’option perl = TRUE. Ainsi :

> gsub("^.*(\\d{4}).*$", "\\1", naiss, perl = TRUE)
[1] "1897" "1893" "1895" "1896" "1908"

Attention, la fonction gsub() laisse inchangées les chaînes de caractères qui ne correspondent pas à la regex :

> gsub("^né .*(\\d{4}).*$", "\\1", naiss, perl = TRUE)
[1] "1897"
[2] "1893"
[3] "née en 1895, à Tauriac (47)"
[4] "1896"
[5] "née le 1er janvier 1908, à Końskowola - Pologne (99)"

À noter que dans ces deux fonctions on peut utiliser l’option ignore.case = TRUE, qui permet de ne pas tenir compte de la casse des lettres et traîter de manière équivalente majuscules et minuscules, ce qui peut être utile.

Ces fonctions font partie des outils de base en R. Il en existe d’autres, qui peuvent être plus pratiques, dans des modules dédiés à la manipulation de texte.

Modules utiles pour traiter du texte

stringr

Le module stringr dispose, pour les mêmes usages, de str_detect(vecteur, regex) (attention à l’inversion !), et de str_replace_all(vecteur, regex, remplacement). Plus précisément, str_detect() fait le même travail que grepl(). Il existe aussi str_extract(vecteur, regex), qui peut être très pratique, et correspond exactement à ce qu’on veut ici. On les invoque, ainsi que la syntaxe perl, de la manière suivante :

> str_detect(naiss, perl("\\d{4}"))
[1] TRUE TRUE TRUE TRUE TRUE
> str_replace_all(naiss, perl("^.*(\\d{4}).*$"), "\\1")
[1] "1897" "1893" "1895" "1896" "1908"
> str_extract(naiss, perl("\\d{4}"))
[1] "1897" "1893" "1895" "1896" "1908"

stringi

Très complet pour travailler avec des caractères exotiques et de manière générale un excellent outil pour traiter du texte en R, le module stringi comprend les fonctions stri_detect_regex(vecteur, regex) et stri_replace_all_regex(vecteur, regex, remplacement), ainsi que stri_extract_first_regex(vecteur, pattern) (et d’autres du même type, comme stri_extract_all_regex(vecteur, pattern)). Il n’est pas nécessaire d’indiquer que l’on utilise des regex de type perl, car c’est la syntaxe qu’il utilise par défaut[6]. Attention, ici les éléments mis en mémoire se rappellent avec $1, $2 etc., plutôt que \\1, \\2, etc., cf. deuxième des lignes suivantes :

> stri_detect_regex(naiss, "\\d{4}")
[1] TRUE TRUE TRUE TRUE
> stri_replace_all_regex(naiss, "^.*(\\d{4}).*$", "$1")
[1] "1897" "1893" "1895" "1896" "1908"
> stri_extract_first_regex(naiss, "\\d{4}")
[1] "1897" "1893" "1895" "1896" "1908"

Avec LibreOffice/OpenOffice

Nul besoin de passer par un logiciel de statistiques pour utiliser des expressions régulières. LibreOffice et OpenOffice peuvent également effectuer des recherches et des remplacement de cette manière. La syntaxe qu’ils utilisent est documentée en détail ici pour LibreOffice, là pour OpenOffice, mais dans les deux cas les bases sont les mêmes que ce qu’on a présenté ici.

Pour les utiliser, il faut aller dans le menu “Édition” et y choisir “Rechercher & Remplacer”; puis, dans la fenêtre qui s’ouvre alors, cocher la case “Expressions régulières”. Les valeurs mises en mémoire se retrouvent avec $1, $2, etc. Pour notre exemple, cela donne :

regex-libreoffice-replace
Rechercher/Remplacer sous LibreOffice

Et on obtient le même résultat qu’avec R.

Pour Word la fonction de recherche/remplacement avancée est basée sur une syntaxe assez différente, appelée fréquemment wildcards. Elle est moins pratique sur certains aspects, mais permet bien de décrire le formattage du texte (italique, gras, etc.) – voir la documentation ici.

Conclusion

Voilà, vous êtes maintenant équipé pour transformer le texte de départ en un tableau de données ou un dataframe, avec les expressions régulières. Vous pouvez vous entraîner, par exemple en R, à créer un dataframe dont les variables (colonnes) seraient l’année de naissance, le mois de naissance, mais aussi le sexe, et le département voire le pays de naissance, de chaque individu.

Dans des prochains billets, on verra comment traiter des cas plus complexes, en termes d’éléments de syntaxe mais aussi d’outils logiciels pour aider à appréhender des regex complexes.


  1. Sélectionner manuellement l’année dans une ligne et en effacer le reste prendra quelques secondes, en fonction de la maîtrise des raccourcis clavier par l’opérateur. Pour quelques cas c’est acceptable, mais pour mille cas cela revient facilement à plusieurs heures, sans compter l’épuisement de l’opérateur. En revanche, écrire le “programme” nécessaire pour le faire de manière automatisée prend quelques dizaines de secondes, et l’exécution est quasiment instantanée.  ↩

  2. Le terme français considéré plus correct, mais presque jamais employé, est celui d’expressions rationnelles.  ↩

  3. Le nom de PCRE signifie Perl-Compatible Regular Expressions. Cette dénomination est le nom d’une syntaxe d’expressions régulières compatibles avec celles de Perl, un langage créé dans les années 1980 précisément pour faire des manipulations de texte, et qui a longtemps été la référence dans ce domaine. En fait deux syntaxes sont disponibles en R, POSIX et PCRE, mais la base est la même. Celle de Word est assez différente, et plus restreinte, mais il utile de la connaître ou au moins de savoir ce qu’on peut en faire; on l’évoquera prochainement.  ↩

  4. Les groupes de caractères sont aussi appelés classes de caractères.  ↩

  5. La raison en est qu’on écrit les regex sous la forme de chaînes de caractères, qui ont une syntaxe pour écrire certains caractères spéciaux (proche de celle mentionnée ici pour les caractères spéciaux des regex), que R interprète ensuite pour y lire la “vraie” chaîne de caractères que l’on a voulu exprimer. Comme le backslash (\) est l’un de ces caractères spéciaux, et qu’on écrit justement les caractères spéciaux en les faisant précér eux-mêmes d’un backslash, la syntaxe pour communiquer un backslash à R dans une chaîne de caractères est \\ (i.e. "… \\ …"). Pour voir comment R interprète les chaînes de caractères, on peut utiliser la fonction cat() : essayer par exemple cat("\"), cat("\\"), cat("\d"), cat("\\d").  ↩

  6. Plus précisément stringi est basé sur la syntaxe ICU, mais qui est très proche de PCRE.  ↩


Alexandre Hobeika

Docteur en sociologie politique de l'EHESS, CMH-ETT

More Posts - Website

11 réflexions au sujet de « Les expressions régulières : le super rechercher-remplacer »

  1. Ping : Une alternative à Word : écrire en RMarkdown | Data Sciences Sociales

  2. Vincent

    très intéressant !
    Petite bouteille à la mer cependant : j’ai une regex « ^[^\\]*\\n([^\\]+).* » marche très bien sur regex101 mais je n’arrive pas à la retranscrire pour qu’elle fonctionne dans R :(((

    Répondre
    1. Alexandre Hobeika Auteur de l’article

      Bonjour,
      Si vous essayez avec [^\\\\] à la place de [^\\], est-ce que ça fonctionne ?
      Sinon n’hésitez pas à donner une ou deux lignes auxquelles vous voulez appliquer la regex.

      Répondre
      1. Vincent

        Merci beaucoup pour la réponse.
        Exemple, avec la regex qui marche comme je le veux :
        https://regex101.com/r/E4HyMZ/1
        à noter que les « \n » sont bel est bien « littérales » (il ne s’agit pas de retours chariots)
        En faisant comme vous le proposez c’est déjà mieux (pour que R lise \\ il faut donc \\\\, j’ai eu tord de m’arrêter à \\\ !), je n’ai plus de message d’erreur de R, mais n’obtient pas le résultat escompté non plus (les parenthèses ne « prennent » que un « \n »)

        Répondre
  3. Ping : Notice technique, illustrée par la communication officielle du Front national | Polit'bistro : des politiques, du café

  4. Ping : Notice technique, illustrée par la communication officielle du Front national | Polit’bistro : des politiques, du café

  5. étienne

    Merci Alex pour ce billet aussi riche et clair à la fois. Peux tu préciser ce que fait l’option perl=T, que tu évoques dans la partie « application sous R »?

    Répondre
  6. Ping : [Zotero] Renommer ses documents avec la date *complète* | LASPIC | Carnet

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *