text1 <- "Mit doppelten Anführungszeichen"
text2 <- 'Wenn "Anführungszeichen" im Text vorkommen klappen die einfachen'
11 Strings
11.1 Voraussetzungen und erste Beispiele
- In diesem Kapitel werden die Grundlagen des Pakets
stringr
behandelt. - Man kann Zeichenketten, die auch Strings genannt werden, mit Hilfe von einfachen oder doppelten Anführungszeichen erstellen.
- Das einfache Beispiel zeigt schon, dass einige Sonderzeichen anders behandelt werden müssen. Man nennt diese Sonderzeichen auch Escape-Sequenzen.
x <- c("\'", "\"", "\\", "\u00b5") # einfach, doppelt, Backslash, µ
x
[1] "'" "\"" "\\" "µ"
writeLines(x)
'
"
\
µ
Man sieht, dass die gedruckte Darstellung des Strings und der String selbst nicht automatisch das Gleiche ist!
Die komplette Liste der Escape-Sequenzen bekommt man z.B. mit der Hilfe zu Anführungszeichen:
help('"')
11.2 Zusammenfügen von Strings
- Das Zusammenfügen von Zeichenketten geschieht mit der Funktion
str_c()
. Dabei kann ein Separator mit dem Argumentsep=
angegeben werden. Außerdem werden Vektoren recycled.
str_c("x", "y")
[1] "xy"
str_c("x", "y", "z", sep = ", ") # Separator
[1] "x, y, z"
str_c("x", c("y","z")) # vektorisiert -> recycling
[1] "xy" "xz"
str_c(c("a", "b", "c"), collapse = ", ") # kollabiert einen Vektor
[1] "a, b, c"
- Im Allgemeinen bleiben
NA
s auch nach dem ZusammenfügenNA
s, es sei denn sie sollen explizit als Zeichenkette behandelt werden. Dies ist der wesentliche Unterschied zur Funktionpaste()
aus Base-R.
x <- c("abc", NA)
str_c("+|", x, "|+") # NA bleibt NA
[1] "+|abc|+" NA
str_c("+|", str_replace_na(x), "|+") # NA wird wie Zeichenkette behandelt
[1] "+|abc|+" "+|NA|+"
11.3 Teile von Zeichenketten
- Mit der Funktion
str_sub()
kann auf Teile der Zeichenkette zugegriffen werden. Dabei geben die Argumentestart=
undend=
die betroffenen Positionen an.
x <- c("Kanne", "Rand", "Laden")
str_sub(x, 1, 3)
[1] "Kan" "Ran" "Lad"
str_sub(x, -3, -1) # Negative Zahlen zählen von hinten
[1] "nne" "and" "den"
- Es spielt keine Rolle, wenn die Zeichenkette zu kurz ist
y <- c("A")
str_sub(y, 1, 5)
[1] "A"
- Es ist auch möglich Zuweisungen zu machen:
str_sub(x,1,1) <- "w"
x
[1] "wanne" "wand" "waden"
str_sub(x,1,1) <- str_to_upper(str_sub(x,1,1))
x
[1] "Wanne" "Wand" "Waden"
11.3.1 Locales
- Wir hatten schon die Funktion
str_to_upper()
gesehen. Analog gibt es auch
[1] "DAS IST EIN STRING" "IN" "3 TEILEN"
str_to_lower(x)
[1] "das ist ein string" "in" "3 teilen"
str_to_title(x)
[1] "Das Ist Ein String" "In" "3 Teilen"
str_to_sentence(x)
[1] "Das ist ein string" "In" "3 teilen"
- Gegebenenfalls muss das Argument
locale=
gesetzt werden.
# Türkisch hat zwei i's: mit und ohne Punkt. Bei Kapitalisieren muss das
# ggf. berücksichtigt werden durch setzen eines locale-Arguments:
str_to_upper(c("i", "ı"))
[1] "I" "I"
str_to_upper(c("i", "ı"), locale = "tr")
[1] "İ" "I"
- Auch die Funktionen
str_sort()
undstr_order()
haben das Argumentlocale=
, da verschiedene Sprachen verschiedene Ordnungen in den Alphabeten haben.
11.4 Reguläre Ausdrücke
Einfaches Identifizieren von Mustern
- Ein wichtiges und effizientes Werkzeug um Muster in Zeichenketten zu identifizieren sind reguläre Ausdrücke.
- Um das identifizierte Muster zu visualisieren, bieten sich die Funktion
str_view()
an.
punkt_und_backslash <- c("\\.", "\\\\")
writeLines(punkt_und_backslash)
\.
\\
x <- c("Apfel", "Banane", "Mango", "Traube")
str_view(x, "an")
[2] │ B<an><an>e
[3] │ M<an>go
x <- c("Apfel", "Banane", "Mango", "Traube")
str_view(x, ".a.")
[2] │ <Ban>ane
[3] │ <Man>go
[4] │ T<rau>be
Während die Syntax im oberen Beispiel klar sein sollte, ist im unteren Beispiel der Punkte, nicht sofort klar: es wird nicht nach Punkten in einem Ausdruck gesucht, sondern die Punkte sind Platzhalter. Ein Frage, die sich nun stellt ist Wie indentifiziert man “.”? Dazu muss benötigt man eine Escape-Sequenz.
11.4.1 Anker und Zeichenklassen
Reguläre Ausdrücke betreffen prinzipiell jeden Teil eines Strings. Manchmal möchte man den Anfang oder das Ende betreffend oder nach Klassen von Zeichen suchen.
Anfang oder Ende:
\^
der Anfang eines Strings\$
das Ende eines Strings-
Klassen von Zeichen:
-
\d
eine beliebige Ziffer, -
\s
ein beliebiges Leerzeichen (z.B. Leertaste, Tab, neue Zeile), -
[abc]
die Buchstaben a, b oder c, -
[\^{}abc]
alles außer die Buchstaben a, b oder c.
-
Wichtig: Um einen regulären Ausdruck mit \d
oder \s
zu generieren, muss \
‘escaped’ werden, und man schreibt:
writeLines(c("\\d", "\\s"))
\d
\s
x <- c("abcd", "dcba", "a")
# Anfang der Zeichenkette
str_view(x, "^a")
[1] │ <a>bcd
[3] │ <a>
# Ende der Zeichenkette
str_view(x, "a$")
[2] │ dcb<a>
[3] │ <a>
Beispiele: Zeichenklassen
x <- c("1 2 3 abc", "ABC 123", "abc ABC")
str_view(x, "[bcd]")
[1] │ 1 2 3 a<b><c>
[3] │ a<b><c> ABC
str_view(x, "\\d")
[1] │ <1> <2> <3> abc
[2] │ ABC <1><2><3>
Mit Hilfe von |
kann zwischen alternativen Möglichkeiten gesucht werden (Vorsicht, schwache Bindungsstärke):
str_view(c("Stefan", "Stephan"), "Ste(f|ph)an")
[1] │ <Stefan>
[2] │ <Stephan>
str_view(c("Stefan", "Stephan"), "Stef|phan")
[1] │ <Stef>an
[2] │ Ste<phan>
- Eine Zeichenklasse, die genau ein Zeichen enthält, ist eine Alternative zu Backslash-Escape Sequenzen.
- Dies funktioniert mit fast allen Sonderzeichen:
\$
,.
|
,?
*
,+
,(
,)
,]
,{
,}
- Allerdings nicht mit
[
,\
,^
,-
da diesen eine besondere Bedeutung innerhalb der regulären Ausdrücke zukommt. Diese müssen mittels Backslash escaped werden.
y <- c("abc", "a.c", "a*c", "a c")
str_view(y, "a[.]c")
[2] │ <a.c>
str_view(y, ".[*]c")
[3] │ <a*c>
str_view(y, "a[ ]")
[4] │ <a >c
11.5 Wiederholungen
-
Der nächste Schritt ist zu kontrollieren wie oft ein Muster auftaucht:
-
?
: 0 oder 1 -
+
: 1 oder mehr -
*
: 0 oder mehr
-
Die Bindungsstärke der Operatoren ist sehr hoch, so dass meist runde Klammern gesetzt werden müssen. Der Ausdruck
wie?der
ist sowohlwider
als auchwieder
.-
Es ist auch möglich die Zahl der Treffer genau zu spezifizieren:
-
{n}
: genau n -
{n,}
: n oder mehr -
{,m}
: höchstens m -
{n,m}
: zwischen n und m
-
Dabei wird der längste mögliche String herausgesucht. Setzt man ein ?
hinter den Ausdruck, so wird nur die Mindestlänge herausgesucht.
x <- "1888 ist MDCCCLXXXVIII"
str_view(x, "CC?")
[1] │ 1888 ist MD<CC><C>LXXXVIII
str_view(x, "C[LX]+")
[1] │ 1888 ist MDCC<CLXXX>VIII
str_view(x, "C{2,3}")
[1] │ 1888 ist MD<CCC>LXXXVIII
str_view(x, "C{2,3}?")
[1] │ 1888 ist MD<CC>CLXXXVIII
11.5.1 Gruppierungen und Rückverweisenen
- Mit den runden Klammern
()
kann man nicht nur komplexe Ausdrücke eindeutig formulieren, sie erlauben es auch nummerierte (1, 2, usw.) Gruppen (numbered capturing group) zu bilden. - Dabei wird der reguläre Ausdruck innerhalb der Klammern gespeichert, und es ist möglich mittel
\1
,\2
etc. darauf zu verweisen.
str_view(fruit, "(..)\\1", match = TRUE)
[4] │ b<anan>a
[20] │ <coco>nut
[22] │ <cucu>mber
[41] │ <juju>be
[56] │ <papa>ya
[73] │ s<alal> berry
str_view(fruit, "(o)\\1", match = TRUE)
[9] │ bl<oo>d orange
[33] │ g<oo>seberry
str_view(fruit, "(.)(.)\\2\\1", match = TRUE)
[5] │ bell p<eppe>r
[17] │ chili p<eppe>r
str_view(fruit, "(.)(.)(.)\\2\\1", match = TRUE)
[4] │ b<anana>
Anwendungen
Nachdem reguläre Ausdrücke nun bekannt sind, wird es Zeit diese sinnvoll einzusetzen. Folgende Themen sollen dazu behandelt werden:
- Zeichenketten finden, die ein bestimmtes Muster haben,
- Positionen eines Musters finden,
- Muster extrahieren,
- Muster ersetzen,
- Strings aufgrund eines Musters teilen.
Reguläre Ausdrücke sind sehr mächtig, und ist verlockend Probleme mit einem einzigen regulären Ausdruck zu lösen. Aber
,,Some people, when confronted with a problem, think ‘I know, I’ll use regular expressions.’ Now they have two problems.``
(Jamie Zawinski)
Ein Beispiel ist dieser reguläre Ausdruck, der überprüft, ob eine E-Mailadresse zulässig ist:
11.6 Übereinstimmungen erkennen
- Ob ein Muster ist einer Zeichenkette erhalten ist oder nicht kann mit der Funktion
str_detect()
herausgefunden werden.
x <- c("Apfel", "Birne", "Dattel")
str_detect(x, "l")
[1] TRUE FALSE TRUE
- Da die logischen Werte
TRUE
undFALSE
in einem numerischen Kontext zu1
und0
werden, ist es möglich auf längeren Vektoren statistische Kenngrößen zu berechnen.
# Einlesen 370105 engl. Wörter
words <- read_lines("https://tinyurl.com/3t569b4v")
# Wörter der Länge 5:
wordle <- words[str_length(words) == 5]
# eine Übersicht:
summary(wordle)
Length Class Mode
15921 character character
# Wie viele starten mit einem t:
sum(str_detect(wordle, "^t"))
[1] 981
# Wie viele enthalten Doppelkonsonanten:
sum(str_detect(wordle, "([^aeiou])\\1"))
[1] 1358
# Wie viele haben keine Vokale:
sum(!str_detect(wordle, "[aeiou]"))
[1] 54
sum(str_detect(wordle, "^[^aeiou]+$"))
[1] 54
# Prozent Wörter enden auf Vokal:
mean(str_detect(wordle, "[aeiou]$"))
[1] 0.2743546
- Um Wörter mit einer bestimmten Sequenz oder eines bestimmten Musters zu filtern, kann die Funktion
str_subset()
verwendet werden:
# kompliziert:
wordle[str_detect(wordle, "axy$")]
[1] "ataxy" "braxy" "coaxy" "flaxy"
# oder die Kurzform:
str_subset(wordle, "axy$")
[1] "ataxy" "braxy" "coaxy" "flaxy"
- Sind die Daten in einem Dataframe, so können die Funktionen analog in der Funktion
filter()
verwendet werden.
# A tibble: 2 × 2
woerter i
<chr> <int>
1 rebuy 11404
2 upbuy 14914
- Eine Variation von
str_detect()
iststr_count()
, dabei wird die Anzahl der Übereinstimmungen in einer Zeichenkette gezählt.
x <- c("Augsburg", "Atlanta")
str_count(x, "a")
[1] 0 2
# Durchschnittliche Vokalzahl
mean(str_count(wordle, "[aeiou]"))
[1] 1.874443
- Es ist zu bemerken, dass Treffer niemals überlappen, wie das folgende Beispiel zeigt:
# liefert 2 und nicht 3 Treffer:
str_count("abababa", "aba")
[1] 2
11.7 Übereinstimmungen extrahieren
- Um den Text einer Übereinstimmung herauszuziehen benutzt man die Funktion
str_extract()
.
- Um die Funktionsweise zu illustrieren nehmen wir die folgenden Daten:
length(sentences)
[1] 720
head(sentences)
[1] "The birch canoe slid on the smooth planks."
[2] "Glue the sheet to the dark blue background."
[3] "It's easy to tell the depth of a well."
[4] "These days a chicken leg is a rare dish."
[5] "Rice is often served in round bowls."
[6] "The juice of lemons makes fine punch."
- Als Beispiel sollen nun die in den Sätzen erhaltenen Farben extrahiert werden. Dazu bildet man einen regulären Ausdruck, der die Farben enthält:
colours <- c("red", "orange", "yellow", "green", "blue", "purple")
colour_match <- str_c(colours, collapse = "|")
colour_match
[1] "red|orange|yellow|green|blue|purple"
- Nun filtert man die Sätze heraus, die mindestens eine der Farben enthalten
has_colours <- str_subset(sentences, colour_match)
matches <- str_extract(has_colours, colour_match)
head(matches)
[1] "blue" "blue" "red" "red" "red" "blue"
- Die Funktion
str_extract()
extrahiert immer nur den ersten Treffer.
Durch Auswählen der Sätze, die mehr als eine Übereinstimmung haben.
more <- sentences[str_count(sentences, colour_match) > 1]
str_view(more, colour_match)
- Mit der Funktion
str_extract_all()
erhält man alle Übereinstimmungen. Das Ergebnis ist eine Liste. Listen sind eine praktische Datenstruktur, auf die wir später nochmal zurückkommen. Wählt man zusätzlich das Argumentsimplify = TRUE
, so erhält man eine Matrix, eine weitere Datenstruktur, die nicht nur für die lineare Algebra praktisch ist ;-).
x <- c("a", "b-c", "a.b.c")
str_extract_all(x, "[a-z]", simplify = TRUE)
[,1] [,2] [,3]
[1,] "a" "" ""
[2,] "b" "c" ""
[3,] "a" "b" "c"
11.8 Gruppierte Übereinstimmungen
Für die runden Klammern
(
)
gibt es neben den bisher besprochenen noch eine weitere Anwendung, nämlich um Teile eines komplexen Ausdrucks zu extrahieren.Im folgenden Beispiel ist jedes Wort, dass nach den Artikeln a, an oder the kommt gesucht.
noun <- "(a|an|the) ([^ ]+)"
has_noun <- sentences |>
str_subset(noun) |>
head(6)
has_noun |> str_extract(noun)
[1] "the smooth" "the sheet" "the depth" "a chicken"
[5] "the parked" "the sun"
Bemerkung: Es ist ein wenig kompliziert in einem regulären Ausdruck ein Wort zu definieren. Der obige Ausdruck ist eine einfache Näherung und für viele Anwendungen geeignet (hier allerdings nur sehr bedingt). * Mit der Funktion str_match()
erhält man jede einzene Komponente in Form einer Matrix:
has_noun |> str_match(noun)
[,1] [,2] [,3]
[1,] "the smooth" "the" "smooth"
[2,] "the sheet" "the" "sheet"
[3,] "the depth" "the" "depth"
[4,] "a chicken" "a" "chicken"
[5,] "the parked" "the" "parked"
[6,] "the sun" "the" "sun"
- Hat man die Daten in einem Tibble, so ist die Funktion
tidyr::extract()
einfacher zu benutzen. Diese macht das gleiche wiestr_match()
, allerding muss man für die Übereinstimmungen Namen angeben, da das Ergebnis als eigene Merkmale gespeichert werden.
tibble(sentence = sentences) |>
tidyr::extract(
sentence, c("article", "noun"), "(a|the) ([^ ]+)",
remove = FALSE
)
# A tibble: 720 × 3
sentence article noun
<chr> <chr> <chr>
1 The birch canoe slid on the smooth planks. the smooth
2 Glue the sheet to the dark blue background. the sheet
3 It's easy to tell the depth of a well. the depth
4 These days a chicken leg is a rare dish. a chicken
5 Rice is often served in round bowls. <NA> <NA>
6 The juice of lemons makes fine punch. <NA> <NA>
7 The box was thrown beside the parked truck. the parked
8 The hogs were fed chopped corn and garbage. <NA> <NA>
9 Four hours of steady work faced us. <NA> <NA>
10 A large size in stockings is hard to sell. <NA> <NA>
# ℹ 710 more rows
- Analog zu
str_match()
benötigt manstr_match_all()
, wenn alle Übereinstimmungen pro String erkannt werden sollen.
11.8.1 Übereinstimmungen ersetzen
- Die Funktion
str_replace()
undstr_replace_all()
erlauben es Übereinstimmungen durch andere Zeichenketten zu ersetzen. - Mit der Funktion
str_replace_all()
können auch mehrere (verschiedene) Ersetzungen gleichzeitig durchgeführt, wenn die ausgetausche Zeichenkette ein Vektor ist. - Man kann auch rückverweisen um Übereinstimmungen zu ersetzen oder zu tauschen (im Beispiel wird das zweite und das dritte Wort getauscht).
x <- c("Apfel", "Birne", "Clementine")
str_replace(x, "[aeiou]", "-")
[1] "Apf-l" "B-rne" "Cl-mentine"
str_replace_all(x, "[aeiou]", "-")
[1] "Apf-l" "B-rn-" "Cl-m-nt-n-"
x <- c("1 Auto", "2 Kinder", "3 CDs")
str_replace_all(x, c("1" = "ein", "2" = "zwei", "3" = "drei"))
[1] "ein Auto" "zwei Kinder" "drei CDs"
sentences |>
str_replace("([^ ]+) ([^ ]+) ([^ ]+)", "\\1 \\3 \\2") |>
head(4)
[1] "The canoe birch slid on the smooth planks."
[2] "Glue sheet the to the dark blue background."
[3] "It's to easy tell the depth of a well."
[4] "These a days chicken leg is a rare dish."
11.9 Splitting
- Die Funktion
str_split()
zerlegt eine Zeichenkette. Die Argumente sind -
string=
: die zu zerlegende Zeichenkette, -
pattern=
: das Muster bei dem die Zeichenkette getrennt wird. Dieses wird komplett entnommen, ist also in der getrennten Ausgabeliste nicht mehr enthalten, -
n=Inf
: Anzahl der zurückgegebenen Stücke, und -
simplify=FALSE
: Rückgabe ist eine Liste, fallsTRUE
wird eine Zeichenketten-Matrix zurückgegeben.
sentences |> head(3) |> str_split(" ")
[[1]]
[1] "The" "birch" "canoe" "slid" "on" "the"
[7] "smooth" "planks."
[[2]]
[1] "Glue" "the" "sheet" "to"
[5] "the" "dark" "blue" "background."
[[3]]
[1] "It's" "easy" "to" "tell" "the" "depth" "of" "a"
[9] "well."
str_split(c("a|b|c|d"), "\\|")
[[1]]
[1] "a" "b" "c" "d"
11.10 Splitting und Muster finden
# als Matrix
sentences |> head(3) |> str_split(" ", simplify = TRUE)
[,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,] "The" "birch" "canoe" "slid" "on" "the" "smooth"
[2,] "Glue" "the" "sheet" "to" "the" "dark" "blue"
[3,] "It's" "easy" "to" "tell" "the" "depth" "of"
[,8] [,9]
[1,] "planks." ""
[2,] "background." ""
[3,] "a" "well."
# nur je 2 Vektoren
sentences |> head(3) |> str_split(" ", n = 2, simplify = TRUE)
[,1] [,2]
[1,] "The" "birch canoe slid on the smooth planks."
[2,] "Glue" "the sheet to the dark blue background."
[3,] "It's" "easy to tell the depth of a well."
- Mit den Funktionen
str_locate()
undstr_locate_all()
gibt den Start-und Enpunkt eines Musters. Dies ist genau dann sinnvoll, wenn keine der anderen Funktionen exakt das macht was man braucht. (\implies?str_locate
) - Mit
str_locate()
können passende Muster gefunden werden, mitstr_sub()
extrahiert oder geändert werden.