9 Tutorial 9: Suche & Manipulation von String-Patterns

In Tutorial 9 lernen Sie:

  • was String Patterns & Regular Expressions sind
  • wie Sie nach bestimmten String-Pattern suchen
  • wie Sie String-Pattern manipulieren

Ein wichtiger Hinweis: Eine gute Übersicht an Funktionen zur Suche & Manipulation von String-Patterns bietet das folgende Handbuch:

  • Sanchez, G. (2014). Handling and Processing Strings in R. Link sowie Link

Mein Tipp: Haben Sie das PDf von Sanchez nebenbei offen. Sie werden es immer mal wieder brauchen, um nützliche Funktionen zur Manipulation von Text & Regular Expressions nachzuschauen. Ich gebe hier nur eine sehr kurze Einführung in die wichtigsten Funktionen und Regular Expressions.

9.1 Regular Expressions

Mit String-Patterns sind Abfolgen von Zeichen wie Buchstaben, Nummern oder Sonderzeichen gemeint - beispielsweise bestimmte Wörter, nach denen Sie in Texten suchen wollen: “hallo”, “Meine Hausnummer ist 86A” oder “[K|k]limawandel” wären String-Patterns.

Regular Expressions sind ebenso String-Patterns, d.h. Abfolgen von Zeichen wie Buchstaben, Nummern oder Sonderzeichen. Allerdings meint man mit Regular Expressions meist spezifische String-Patterns, in denen Zeichenabfolgen nicht wörtlich gedeutet werden, sondern in denen bestimmte Zeichenabfolgen auf eine andere, nicht wörtliche Bedeutung verweisen. Dadurch erlaubt uns die Arbeit mit Regular Expressions flexible Suchen nach bestimmten Patterns. Das lässt sich am besten an einem Beispiel verdeutlichen.

Nehmen wir an, Sie hätten den folgenden Character-Vektor.

(Sie können den options-Befehl hier ignorieren - dieser sorgt nur dafür, dass Sie den Text in einem vernünftigen Format angezeigt bekommen)

Nehmen wir an, Sie wollen nach einem bestimmten Wort suchen, nämlich nach dem Wort Programm. Ich nenne dieses Wort/die Wörter, nach denen wir suchen, im Folgenden pattern, weil das der Begriff ist, der in vielen R-Funktionen genutzt wird.

Sie wollen also wissen, in welchen Sätzen, die im Vektor texts abgespeichert wurden, das Wort Programm vorkommt. Sie nutzen dafür die Funktion grep, die im nächsten Abschnitt (9.2) nochmal genauer erklärt wird (ich erläutert sie daher hier nur kurz).

grep(x, pattern, value = TRUE) sucht nach einem bestimmten pattern und gibt uns nur den Inhalt der Elementen eines Objektes x zurück, in dem unser pattern gematched wird.

Kurz: Wir weisen R an, alle Sätze auszugeben, in denen das Wort Programm vorkommt.

Was müssen wir in der grep-Funktion also spezifizieren?

  • pattern: String, nach dem wir suchen: Programm.
  • x: ein Character-Vektor, in dem wir nach unserem pattern suchen: texts
  • value: ob uns R die Position der Sätze zurückgeben soll, die unser pattern enthalten, oder den Inhalt der Sätze. Da wir den Inhalt der Sätze enthalten wollen: TRUE (bei FALSE würde R einfach die Position der Sätze wiedergeben, in denen das Wort Programm vorkommt)

Und so sieht der Befehl dann aus:

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

Sie sehen: Der erste und der zweite Satz im Vektor texts enthalten das Wort Programm.

Sie haben jetzt nach dem pattern Programm gesucht. R sucht also einfach nach der Buchstabenabfolge P, r, o, g, r, a, m, m.

In vielen Fällen müssen wir aber mit unseren Suchbegriffen flexibler sein - z.B. kann es sein, dass wir wissen wollen, ob das Wort “Programm” oder das Wort “programmieren” vorkommt.

Natürlich könnten wir dafür einzeln nach Programm und programmieren suchen.

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."
## [1] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

Einfacher als zwei getrennte Suchen durchzuführen, wäre logische Operatoren, Character Classes oder Metacharacters nutzen. All dies sind vorgegebene Regular Expressions, bei denen Buchstaben, Zahlen oder Zeichen nicht für sich selbst stehen, sondern vom Computer einer bestimmten Bedeutung zugeordnet werden. Diese Zuordnung erlaubt es uns, flexibler mit Strings in R zu arbeiten. Kommen wir direkt zu einem Beispiel.

9.1.1 Logische Operatoren

Eigentlich wollen wir R anweisen, uns Sätze auszugeben, in denen die Wörter Programm oder porgrammieren vorkommen. Praktischer wäre, wir könnten dies in einem Befehl machen.

Das lässt sich ganz einfach umsetzen, in dem wir den Operator | nutzen. Sie kennen diesen schon aus Tutorial 3: Objekte & Strukturen in R. Wir suchen mit einem pattern Sätzen, in denen “Programm” oder “programmieren” vorkommt.

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

R weiss dabei, dass er das Zeichen | nicht “wörtlich” interpretieren soll - denn sonst würde uns das Programm nur die Texte ausgeben, in denen die Zeichenabfolge "Programm|program genau so vorkommt.

Stattdessen übersetzt R das Zeichen | korrekt: Das Programm gibt uns Texte aus, in denen Programm oder programmieren vorkommt.

9.1.2 Character Classes

Regular Expressions erlauben es uns auch, nach bestimmten Arten von Characters zu suchen. Dafür können wir mit Character Classes arbeiten. Wenn wir beispielsweise nach dem pattern [a-z] suchen, sucht R nicht nach dem Zeichen [, dann dem Zeichen a, dann dem Zeichen -, dann dem Zeichen z, und dann dem Zeichen ].

Stattdessen weiss R, dass mit dem pattern [a-z] ein beliebiger, kleingeschriebener Buchstabe gesucht wird.

Hier finden Sie eine (begrenzte) Auswahl häufig genutzter Regular Expressions zu Character Classes:

Table 9.1: Character Classes
Character.Classes Bedeutung
[a-z] findet einen beliebigen Buchstaben (kleingeschrieben)
[A-Z] findet einen beliebigen Buchstaben (grossgeschrieben)
[[:alpha:]] findet einen beliebigen Buchstaben (klein- und grossgeschrieben)
[0-9] findet eine beliebige Zahl
[a-zA-Z0-9] findet einen beliebigen Buchstaben (klein- und grossgeschrieben) oder eine Zahl
[[:blank:]] findet u.a. Leerzeichen
[[:punct:]] findet verschiedene Satzzeichen, u.a. ! # : ; .

Wenn wir also die Wörter “Programm” und “programmieren” nicht über eine kombinierte Suchanfrage via | suchen wollen, können wir folgenden Suchstring nutzen. Dieser gibt uns alle Sätze aus, in denen auf einen beliebigen Buchstaben in Klein- oder Grossschreibung die Buchstabenfolge rogramm folgt:

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

9.1.3 Quantifier

Regular Expressions erlauben es uns auch, zu spezifizieren, wie häufig bestimmte Buchstaben, Zahlen oder Zeichen vorkommen sollen. Dafür nutzen wir sogenannte Quantifier.

Table 9.2: Quantifier
Quantifier Bedeutung
? Der vorhergehende Ausdruck kommt maximal einmal vor
+ Der vorhergehende Ausdruck kommt mindestens einmal vor
* Der vorhergehende Ausdruck kommt nie oder mehr vor
{n} Der vorhergehende Ausdruck kommt genau n mal vor
{n,} Der vorhergehende Ausdruck kommt mindestens n mal vor
{n,m} Der vorhergehende Ausdruck kommt mindestens n mal und maximal m mal vor

Wenn wir also z.B. schauen wollen, wie oft die Wörter “Textanalyse” oder “analysieren” vorkommen, könnten wir Quantifier verwenden.

Beispielsweise weisen wir R an, alle Sätze auszugeben, in denen die Buchstabenfolge analy vorkommt. Vor dieser Buchstabenfolge soll maximal einmal die Buchstabenabfolge Text vorkommen, diese kann aber auch gar nicht erscheinen. Deswegen nutzen wir den Quantifier *. Das pattern matched also alle Sätze, in denen das pattern analys vorkommt - wobei davor auch das Wort Text stehen kann.

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

Wieso nutzen wir hier eckige Klammern?

Die eckigen Klammern sind Metacharacters. D.h., R sucht nicht nach den eckigen Klammern im Text, sondern weiss, dass diese nur die konkrete Buchstabenfolge Text identifizieren, auf die sich der Quantifier beziehen soll: Diese Buchstabenfolge in den eckigen Klammern soll maximal einmal vor der Buchstabenfolge analys vorkommen.

9.1.4 Metacharacters

Zu Metacharacters gehören z.B. die folgenden Zeichen (Achtung: Liste nicht abschliessend).

Auch diese werden von R nicht “wörtlich” interpretiert, d.h. das Programm sucht nicht nach diesen im Text. Stattdessen R spricht diesen Zeichen eine bestimmte, andere Bedeutung zu.

Table 9.3: Metacharacters
Metacharacters Escape Fix
* \\* fixed = TRUE
+ \\+ fixed = TRUE
? \\? fixed = TRUE
| \\| fixed = TRUE
{ \\{ fixed = TRUE
} \\} fixed = TRUE
( \\( fixed = TRUE
) \\) fixed = TRUE

Sie kennen einiger dieser Metacharacters bereits: Z.B. wissen Sie, dass das Zeichen * nicht wörtlich gesucht wird, sondern R weiss, dass * für einen Quantifier steht:

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

Was ist aber, wenn wir so einen Metacharacter tatsächlich im Text haben und nach ihm suchen wollen?

Ein Beispiel: Wir wollen wissen, welche Sätze eine Frage sind.

Wir wollen also nach einem Fragezeichen ? suchen, das am Ende eines Satzes steht, dh. vermutlich vor einem beliebigen Buchstaben (klein- und grossgeschrieben) oder einer Zahl:

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."
## [3] "Wer R lernt, merkt: Das ist anstrengend, oder?"

Das hat nicht funktioniert, R gibt uns alle Sätze zurück, obwohl wir sehen, dass nur in einem ein Fragezeichen vorkommt.

Wieso?

Weil ? ein Metacharacter in Form eines Quantifiers ist: R sucht nach allen Sätzen in denen der Ausdruck vor ? mindestens einmal vorkommt. Da vor ? die Zeichenfolge [a-zA-Z0-9] steht, sucht R also nach allen Sätzen, in denen ein beliebiger Buchstabe oder eine beliebige Zahl mindestens einmal vorkommt - und das sind alle Sätze.

Es gibt zwei Lösungen dafür, wie wir mit Metacharacters umgehen. Diese wurden in der Tabelle (zweite und dritte Spalte) schon angedeutet:

Lösung 1: Escape

Wir in der Tabelle oben zu sehen, können wir Metacharacters “escapen”. D.h., wir können R durch Einfügen von zwei Backlashes \\ anweisen, das Zeichen ? nicht als Metacharacter sondern “wörtlich”, d.h. als Character ? zu verstehen. Dann sucht R nach Texten, in denen ein Fragezeichen vorkommt:

## [1] "Wer R lernt, merkt: Das ist anstrengend, oder?"

Lösung 2: Fix

Eine zweite Lösung wäre, R anzuweisen, die gesamte Regular Expression “wörtlich” zu verstehen, d.h. alle Buchstaben, Zahlen und Zeichen genau so zu interpretieren, wie sie dort stehen. R interpretiert dann das Fragezeichen ? nicht in Form eines Quantifiers, sondern sucht nach dem Character ?.

## [1] "Wer R lernt, merkt: Das ist anstrengend, oder?"

9.2 Suche & Manipulation von String-Patterns

Kommen wir nun zu den Funktionen, mit Hilfe derer Sie nach String-Patterns suchen oder diese manipulieren können. Sie haben gerade schon die Funktion grep kennengelernt, mit Hilfe derer Sie sich von R die Sätze ausgeben lassen können, die ein bestimmtes pattern beinhalten. Viele Basis-Funktionen in R, zu denen grep gehört, bieten bereits einige hilfreiche Anwendungen.

Zusätzlich ist das stringr-Package nützlich, das Sie installieren sollten:

Ich gebe im Folgenden einen kurzen Überblick über die wichtigsten Funktionen, mit denen Sie String Patterns suchen oder manipulieren können. Es gibt aber noch eine Vielzahl weiterer hilfreicher Funktionen.

Table 9.4: Funktionen
Funktionsname Package Operation
grep(pattern, x, value=FALSE) Base R Gibt die Position der Elemente im Vektor x wieder, die ein pattern enthalten
grep(pattern, x, value = TRUE) Base R Gibt Inhalte der Elemente im Vektor x wieder, die ein pattern enthalten
grepl(pattern, x) Base R Gibt für alle Elemente im Vektor x wieder, ob diese ein pattern enthalten
sub(pattern, replacement, x) Base R Ersetzt den ersten Match für ein pattern im Vektor x durch replacement
gsub(pattern, replacement, x) stringr Ersetzt alle Matches für ein pattern im Vektor x durch replacement
str_extract(string, pattern) stringr Extrahiert den ersten Match eines patterns im Vektor string
str_extract_all(string, pattern) stringr Extrahiert alle Matches eines patterns im Vektor string
str_count(string, pattern) stringr Zählt, wie oft ein pattern im Vektor string vorkommt

Probieren wir alle Funktionen kurz einmal aus. Wir erinnern uns kurz, wie unsere Sätze aussahen:

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."
## [3] "Wer R lernt, merkt: Das ist anstrengend, oder?"

Wir wollen jetzt schauen, wann und wie Wörter wie programmieren oder Programm in den Sätzen vorkommen (und diese anschliessend manipulieren). Wir suchen dafür nach dem pattern [P|p]rogramm.

9.2.1 Welche Elemente enthalten ein pattern?

Die Funktion grep(pattern, x, value=FALSE) gibt die Position der Elemente im Vektor x wieder, die ein pattern enthalten.

## [1] 1 2

Wir sehen: Satz 1 und 2 enthalten dieses pattern (die Worte Programm und programmieren).

9.2.2 Was sind die Inhalte von Elementen, die ein pattern enthalten?

Die Funktion grep(pattern, x, value=TRUE) gibt die Inhalte derjenigen Elemente im Vektor x wieder, die ein pattern enthalten.

## [1] "Ich lerne heute analysieren mit dem Programm R."                                            
## [2] "Textanalyse zu lernen ist spannend, aber man muss programmieren  mit dem Programm R lernen."

9.2.3 Enthalten Elemente ein pattern?

Die Funktion grepl(pattern, x) gibt für alle Elemente im Vektor x wieder, ob diese ein pattern enthalten.

## [1]  TRUE  TRUE FALSE

9.2.4 Wie kann ich den ersten Match eines Patterns ersetzen?

Eventuell will ich nur das allererste Mal, wenn ein Pattern gematched wird, dieses ersetzen.

Nehmen wir als Beispiel an, ich will das erste Mal, wenn ein Satzzeichen vorkommt, dieses durch das pattern SATZZEICHEN ersetzen. Ich suche also nach dem pattern [:punct:] und will dieses durch das pattern SATZZEICHEN ersetzen.

Die Funktion sub(pattern, replacement, x) ersetzt das erste Mal, wenn ein pattern im Vektor x gematched wird, dieses pattern durch replacement.

## [1] "Ich lerne heute analysieren mit dem Programm RSATZZEICHEN"                                            
## [2] "Textanalyse zu lernen ist spannendSATZZEICHEN aber man muss programmieren  mit dem Programm R lernen."
## [3] "Wer R lerntSATZZEICHEN merkt: Das ist anstrengend, oder?"

9.2.5 Wie kann ich alle Matches eines Patterns ersetzen?

Eventuell will ich immer, wenn ein Pattern gematched wird, dieses ersetzen (nicht nur das erste Mal, wenn dieses vorkommt).

Die Funktion gsub(pattern, replacement, x) ersetzt immer, wenn ein pattern im Vektor x gematched wird, dieses pattern durch replacement.

## [1] "Ich lerne heute analysieren mit dem Programm RSATZZEICHEN"                                                      
## [2] "Textanalyse zu lernen ist spannendSATZZEICHEN aber man muss programmieren  mit dem Programm R lernenSATZZEICHEN"
## [3] "Wer R lerntSATZZEICHEN merktSATZZEICHEN Das ist anstrengendSATZZEICHEN oderSATZZEICHEN"

9.2.6 Wie kann ich das erste Match eines Patterns aus einem Vektor extrahieren?

Vielleicht wollen Sie wissen, mit welchen Wörtern genau über programmieren, Programme, etc. in den Texten gesprochen wird. Sie wollen das erste Match für das pattern [P|p]rogramm im Vektor string extrahieren.

Die Funktion str_extract(string, pattern) extrahiert das allererste match für ein pattern im Vektor string.

Achtung: Diese Funktionen stammen aus dem Package stringr. Sie verwenden daher leicht andere Bezeichnungen für Funktionen (der zu analysierende Vektor x heisst hier bspw. string, nicht x. Die Zeichenfolge, nach der Sie suchen, heisst weiter pattern).

## [1] "Programm" "programm" NA

9.2.7 Wie kann ich alle Matches eines Patterns aus einem Vektor extrahieren?

Vielleicht wollen Sie wissen, mit welchen Wörtern genau über programmieren, Programme, etc. in den Texten gesprochen wird. Sie wollen jetzt nicht das erste, sondern alle Matches für das pattern [P|p]rogramm im Vektor string extrahieren.

Die Funktion str_extract_all(string, pattern) extrahiert alle matches für ein pattern im Vektor string.

## [[1]]
## [1] "Programm"
## 
## [[2]]
## [1] "programm" "Programm"
## 
## [[3]]
## character(0)

9.2.8 Wie kann ich zählen, wie oft ein Pattern vorkommt?

Sagen wir, Sie wollen jetzt zählen, wie oft das pattern [P|p]rogramm im Vektor string vorkommt.

Die Funktion str_count(string, pattern) zählt, wie oft ein pattern im Vektor string genannt wird.

## [1] 1 2 0

9.3 Take Aways

Vokabular:

  • String-Patterns: String-Patterns sind Abfolgen von Zeichen wie Buchstaben, Nummern oder Sonderzeichen.
  • Regular Expression: Regular Expressions sind String-Patterns. Meist enthalten diese aber Zeichen, die nicht wörtlich gedeutet werden, sondern auf eine andere Bedeutung verweisen (Metacharacters).

Befehle:

  • Suchen von Strings: grep(), grepl(),
  • Manipulation von Strings: sub(), gsub()
  • Extrahierung von Strings: str_extract(), str_extract_all()
  • Zählen von Strings: str_count()

9.5 Übungsaufgabe

Bitte speichern Sie die folgenden sechs kurzen Sätze in Ihrem R-Environment als Vektor mit dem Titel sentences ab.

9.5.1 Aufgabe 9.1

Bitte ersetzen sie alle Leerzeichen im Text durch das Wort Leerzeichen und speichern die manipulierten Sätze im Vektor sentences_new ab.

9.5.2 Aufgabe 9.2

Bitte weisen Sie R an, auszugeben, wie oft das pattern climate change in Klein- oder Grossschreibung in jedem Satz im Vektor sentences vorkommt.

9.5.3 Aufgabe 9.3

Wie oft, in Summe, kommt das pattern climate change" in Klein- oder Grossschreibung im Vektor sentences vor?

9.5.4 Aufgabe 9.4

Sagen wir, Sie wollen diese Sätze etwas “formaler” umschreiben.

Ersetzen Sie alle englischen, etwas umgangssprachlichen Verkürzungen in Form des patterns we’ve (egal ob in Klein- oder Grossschreibung) durch das pattern we have und speichern sie die manipulierten Sätze im Vektor sentences_new ab.

Die Lösungen finden Sie bei Lösungen zu Tutorial 9.

Wir machen weiter: mit Tutorial 10: Einlesen & Datentypen.