Regular expressions

Advanced text wrangling…

Author

Søren O’Neill & Steen Harsted

Published

January 6, 2024


When working with text, you will very often need to look for patterns in the text.

This can be as simple as does the text string contain any numbers? or rather more complicated, like which substrings (if any) in the string consists of at least one, but no more than three numbers, followed by a %-sign?

Such pattern detection in strings can be solved using regular expressions:

A regular expression (shortened as regex or regexp), sometimes referred to as a rational expression, is a sequence of characters that specifies a match pattern in text. wikipedia

Regular expressions can be a rather involved affair, but look at these examples to get an idea of the power of regex:

str_detect("I am 142 years old", pattern="[0-9]+")
[1] TRUE

The [0-9] matches numbers between 0 and 9. The + means one or more … i.e. one-or-more numbers.

str_detect("I am 142 years old", pattern="[0-9]{3}")
[1] TRUE

The {3} means exactly three (numbers).

str_detect("I am 142 years old", pattern="^[0-9]+")
[1] FALSE

The ^ means string starts with.

What do you think this regular expression means, and what output will the code generate?

str_detect(my_string, "^[0-3]{1}[0-9]{1}[0-1]{1}[0-9]{1}[0-9]{2}-?[0-9]{4}")

The regular expression means: _string starts with numbers 0-3, exactly once, numbers 0-9 exactly once, numbers 0 or 1 exactly once, ..etc .. the -? means a hyphen zero or one times.

The function will return TRUE if my_string looks like a Danish CPR number: as date of birth (day, month, year), followed by a hyphen (or not) and the four numbers.

What do you think this regular expression does?

str_extract(my_string, "^[0-3]{1}[0-9]{1}[0-1]{1}[0-9]{1}[0-9]{2}(?=(-?[0-9]{4}))$")

The function was changed to str_extract() which will extract the part of my_string that matches the regular expression.

The regular expression has been changed slightly. The (?=(...) means is followed by … and $ means end of the string.

In other words, the regular expression looks for string starts with numbers 0-3, exactly once, numbers 0-9 exactly once, … etc, …followed by a hyphen zero or one times, four numbers and the end of the string — i.e. it extracts the date from a CPR number as a string and then converts it to a date-type variable.

str_extract("031295-1212", "^[0-3]{1}[0-9]{1}[0-1]{1}[0-9]{1}[0-9]{2}(?=(-?[0-9]{4})$)") |>
  as_date(format="%d%m%y")
[1] "1995-12-03"

Read more about regular expressions here and on the stringr cheat-sheet.

Når du arbejder med tekst, vil du ofte være nødt til at lede efter mønstre i teksten.

Dette kan være så simpelt som indeholder tekststrengen tal? eller mere kompliceret, som hvilke understrenge (hvis nogen) i strengen består af mindst ét, men ikke mere end tre tal, efterfulgt af et %-tegn?

Sådan mønsterdetektion i strenge kan løses ved hjælp af regulære udtryk:

Et regulært udtryk (forkortet som regex eller regexp), undertiden omtalt som et rationelt udtryk, er en sekvens af tegn, der angiver et matchmønster i tekst. wikipedia

Regulære udtryk kan være en ret kompliceret affære, men se på disse eksempler for at få en idé om kraften i regex:

str_detect("Jeg er 142 år gammel", pattern="[0-9]+")
[1] TRUE

[0-9] matcher tal mellem 0 og 9. + betyder et eller flere … dvs. et eller flere tal.

str_detect("Jeg er 142 år gammel", pattern="[0-9]{3}")
[1] TRUE

{3} betyder præcis tre (tal).

str_detect("Jeg er 142 år gammel", pattern="^[0-9]+")
[1] FALSE

^ betyder, at strengen starter med.

Hvad tror du, at dette regulære udtryk betyder, og hvilket output vil koden generere?

str_detect(my_string, "^[0-3]{1}[0-9]{1}[0-1]{1}[0-9]{1}[0-9]{2}-?[0-9]{4}")

Det regulære udtryk betyder: _strengen starter med tallene 0-3, præcis én gang, tallene 0-9 præcis én gang, tallene 0 eller 1 præcis én gang, ..osv. .. -? betyder en bindestreg nul eller et antal gange.

Funktionen returnerer TRUE hvis my_string ligner et dansk CPR-nummer: som fødselsdato (dag, måned, år), efterfulgt af en bindestreg (eller ej) og de fire tal.

Hvad tror du, dette regulære udtryk gør?

str_extract(my_string, "^[0-3]{1}[0-9]{1}[0-1]{1}[0-9]{1}[0-9]{2}(?=(-?[0-9]{4}))$")

Funktionen blev ændret til str_extract(), som vil udtrække den del af my_string, der matcher det regulære udtryk.

Det regulære udtryk er blevet ændret en smule. (?=(...) betyder efterfølges af … og $ betyder slutningen af ​​strengen.

Med andre ord leder det regulære udtryk efter strenge, der starter med tallene 0-3, præcis én gang, tallene 0-9 præcis én gang, … osv., …efterfulgt af en bindestreg nul eller en gang, fire tal og slutningen af ​​strengen — dvs. det udtrækker datoen fra et CPR-nummer som en streng og konverterer den derefter til en datotypevariabel.

str_extract("031295-1212", "^[0-3]{1}[0-9]{1}[0-1]{1}[0-9]{1}[0-9]{2}(?=(-?[0-9]{4})$)") |>
as_date(format="%d%m%y")
[1] "1995-12-03"

Læs mere om regular expressions her og på stringr cheat-sheet’et.