11 Tutorial 11: Bereinigung von Text
In Tutorial 11 lernen Sie, wie Sie Text bereinigen können.
Spezifisch behandeln wir folgenden Abschnitte:
- Auf Encoding-Issues prüfen
- Auf Features herunterbrechen & Zahlen, Satzzeichen, etc. entfernen
- Gross- und Kleinschreibung anpassen
- Stopwörter entfernen
- Lemmatizing/Stemming
- Sehr häufige/seltene Wörter entfernen
Wir arbeiten wieder mit einem Text-Korpus, der im R-Package Quanteda bzw. Quanteda-Corpora Quanteda-Corpora-Package zurzeit im Entwicklungsmodus bereits enthalten ist.
Sie finden den Korpus in OLAT (via: Materialien / Datensätze für R) mit dem Namen immigration_news.rda. Bei diesen Dateien handelt es sich um Nachrichtenartikel aus Grossbritannien zum Thema Immigration aus dem Jahr 2014, die (leicht bearbeitet) bereits als R-Environment abgespeichert wurde. Die Daten befinden sich in einem ähnlichen Format, wie Sie sie erhalten würden, wenn Sie ihre Text mit dem readtext-Package einlesen würden.
Quelle der Daten: Nulty, P. & Poletti, M. (2014). “The Immigration Issue in the UK in the 2014 EU Elections: Text Mining the Public Debate.” Presentation at LSE Text Mining Conference 2014. Geladen via dem Quanteda-Corpus_Package.
Bitte laden Sie den Datensatz in Ihr R-Environment.
11.1 Auf Encoding-Issues prüfen
Nachdem Sie die Texte eingelesen haben, sollten Sie IMMER schauen, ob das Einlesen so funktioniert hat, wie Sie sich das vorgestellt haben. Am besten lassen Sie sich dafür probeweise ein paar Texte ausgeben und lesen diese durch:
## [1] "support for ukip continues to grow in the labour heartlands and miliband should be scared\nby leo mckinstry \n933 words\n10 april 2014\n1415\nexpresscouk\nexco\nenglish\ncopyright 2014 \nnigel farage's overwhelming victory in his two debates against nick clegg has transformed the political landscape\nthrough his barnstorming performance farage has not only brought new credibility and popularity to the uk independence party but has also given a highly articulate voice to large swathes of the public who feel betrayed by our arrogant metropolitan elite \nthe impact of farage's triumph was revealed in opinion polls at the weekend that showed a surge in support for ukip one looking at voting intentions for the european parliamentary elections next month put ukip on 30 per cent neck-and-neck with labour there is little doubt that ukip could emerge the biggest party in the euro contest a remarkable outcome for an organisation with no seats at westminster\nconventional wisdom holds that it is cameron's own conservatives who will suffer the most damage from the rise in ukip particularly at the general election in 2015 in this fashionable narrative farage's followers are portrayed as disgruntled right-wing home counties tories spluttering into their gin-and-tonics at the follies of the coalition and the european commission \nin fact most are workingclass and have never voted conservative that's why labour's complacency is so misguided labour strategists have seen ukip's rise as a welcome development that will destroy the tory vote and sweep ed miliband into number 10 but that could be wishful thinking for it now seems the ukip insurgency could be just as big a threat in labour's heartlands \nresearch by academics matthew goodwin and robert ford shows of the 10 most \"ukip-friendly\" constituencies eight are held by labour and \"the largest concentrations of core ukip supporters are not found in tory seats in the shires but in labour fiefdoms such as miliband's in doncaster north\" \nukip's populist message resonates most strongly with those neglected or marginalised by the political establishment all the points nigel farage made so forcefully against clegg could have been directed against miliband the liberal democrat and labour leaders are the tweedledum and tweedledee of progressive orthodoxy obsessed with mass immigration multi-cultural diversity european integration and the green agenda behind a deluge of weasel words miliband is as opposed as clegg to a referendum on britain's eu membership neither truly believes in democracy or in trusting the public\ned miliband's ideological stance shows how labour has abandoned its grassroots [wenn]\nmiliband's ideological stance shows how labour has abandoned its grassroots the party was founded more than a century ago to represent the working class but now in"
Wenn Sie sich den Text so anschauen, sehen Sie, dass hier (scheinbar) noch nicht alles funktioniert hat wie es sollte: Im Datensatz findet sich wiederholt die Frequenz \n vor Wörtern.
Woran liegt das? \n steht in vielen Programmiersprachen für einen Zeilenumbruch. Im Text-File, das eingelesen wurde, kam z.B. nach der Headlines des Artikels ein Zeilenumbruch. Dann folgt die Autor:in des Artikel, dann wieder ein Zeilenumbruch.
Wenn wir uns diesen Text in der Ausgabe ausgeben lassen wollen - also so, wie er Ihnen auch in Ihren Text-Files oder mit der View-Funktion angezeigt wird, sieht alles normal aus:
## support for ukip continues to grow in the labour heartlands and miliband should be scared
## by leo mckinstry
## 933 words
## 10 april 2014
## 1415
## expresscouk
## exco
## english
## copyright 2014
## nigel farage's overwhelming victory in his two debates against nick clegg has transformed the political landscape
## through his barnstorming performance farage has not only brought new credibility and popularity to the uk independence party but has also given a highly articulate voice to large swathes of the public who feel betrayed by our arrogant metropolitan elite
## the impact of farage's triumph was revealed in opinion polls at the weekend that showed a surge in support for ukip one looking at voting intentions for the european parliamentary elections next month put ukip on 30 per cent neck-and-neck with labour there is little doubt that ukip could emerge the biggest party in the euro contest a remarkable outcome for an organisation with no seats at westminster
## conventional wisdom holds that it is cameron's own conservatives who will suffer the most damage from the rise in ukip particularly at the general election in 2015 in this fashionable narrative farage's followers are portrayed as disgruntled right-wing home counties tories spluttering into their gin-and-tonics at the follies of the coalition and the european commission
## in fact most are workingclass and have never voted conservative that's why labour's complacency is so misguided labour strategists have seen ukip's rise as a welcome development that will destroy the tory vote and sweep ed miliband into number 10 but that could be wishful thinking for it now seems the ukip insurgency could be just as big a threat in labour's heartlands
## research by academics matthew goodwin and robert ford shows of the 10 most "ukip-friendly" constituencies eight are held by labour and "the largest concentrations of core ukip supporters are not found in tory seats in the shires but in labour fiefdoms such as miliband's in doncaster north"
## ukip's populist message resonates most strongly with those neglected or marginalised by the political establishment all the points nigel farage made so forcefully against clegg could have been directed against miliband the liberal democrat and labour leaders are the tweedledum and tweedledee of progressive orthodoxy obsessed with mass immigration multi-cultural diversity european integration and the green agenda behind a deluge of weasel words miliband is as opposed as clegg to a referendum on britain's eu membership neither truly believes in democracy or in trusting the public
## ed miliband's ideological stance shows how labour has abandoned its grassroots [wenn]
## miliband's ideological stance shows how labour has abandoned its grassroots the party was founded more than a century ago to represent the working class but now in
Wenn wir uns aber den abgespeicherten Text ausgeben lassen, sehen wir, dass R diese Information (Zeilenumbruch) als String-Pattern \n eingelesen hat.
Das kann problematisch werden: Wenn wir z.B. schauen wollen, ob Text 1 das String-Pattern 933 words 10 April 2014 enthält (was der Text tut, wenn wir ihn uns mit der writeLines-Funktion anschauen, s. Text-Ausgabe oben), würden R uns FALSE zurückgeben:
## [1] FALSE
Warum? Weil nach 933 words ein Zeilenumbruch erfolgt, wie uns die Ausgabe zeigt. Entprechend hat R im Vektor dataframe$text für den ersten Text das String-Pattern 933 words\n10 April 2014 abgespeichert - d.h., der Zeilenumbruch wurde hier also als \n abgespeichert.
Wenn wir den Zeilenumbruch in der Suchanfrage einbeziehen, findet der Computer das richtige pattern:
## [1] TRUE
Am besten ist, Sie lesen die Texte mit dem richtigen Encoding bereits ein. Das geht direkt mit der read_text-Funktion, wenn Sie Text-Files einlesen, wie bereits zuvor erwähnt.
Nicht alle Encoding-Issues werden sich damit aber lösen lassen. Ich würde daher dazu raten, vor der Bereinigung des Textes IMMER einige Texte meines Korpus genau anzuschauen und solche Encoding-Issues ggf. durch Text-Manipulation zu lösen.
Beispielsweise könnten wir die Zeichenabfolge, die hier für einen Zeilenumbruch steht, hier durch ein Leerzeichen ersetzen:
## [1] "support for ukip continues to grow in the labour heartlands and miliband should be scared by leo mckinstry 933 words 10 april 2014 1415 expresscouk exco english copyright 2014 nigel farage's overwhelming victory in his two debates against nick clegg has transformed the political landscape through his barnstorming performance farage has not only brought new credibility and popularity to the uk independence party but has also given a highly articulate voice to large swathes of the public who feel betrayed by our arrogant metropolitan elite the impact of farage's triumph was revealed in opinion polls at the weekend that showed a surge in support for ukip one looking at voting intentions for the european parliamentary elections next month put ukip on 30 per cent neck-and-neck with labour there is little doubt that ukip could emerge the biggest party in the euro contest a remarkable outcome for an organisation with no seats at westminster conventional wisdom holds that it is cameron's own conservatives who will suffer the most damage from the rise in ukip particularly at the general election in 2015 in this fashionable narrative farage's followers are portrayed as disgruntled right-wing home counties tories spluttering into their gin-and-tonics at the follies of the coalition and the european commission in fact most are workingclass and have never voted conservative that's why labour's complacency is so misguided labour strategists have seen ukip's rise as a welcome development that will destroy the tory vote and sweep ed miliband into number 10 but that could be wishful thinking for it now seems the ukip insurgency could be just as big a threat in labour's heartlands research by academics matthew goodwin and robert ford shows of the 10 most \"ukip-friendly\" constituencies eight are held by labour and \"the largest concentrations of core ukip supporters are not found in tory seats in the shires but in labour fiefdoms such as miliband's in doncaster north\" ukip's populist message resonates most strongly with those neglected or marginalised by the political establishment all the points nigel farage made so forcefully against clegg could have been directed against miliband the liberal democrat and labour leaders are the tweedledum and tweedledee of progressive orthodoxy obsessed with mass immigration multi-cultural diversity european integration and the green agenda behind a deluge of weasel words miliband is as opposed as clegg to a referendum on britain's eu membership neither truly believes in democracy or in trusting the public ed miliband's ideological stance shows how labour has abandoned its grassroots [wenn] miliband's ideological stance shows how labour has abandoned its grassroots the party was founded more than a century ago to represent the working class but now in"
Sie werden mit solchen Arten von Encoding-Problemen immer wieder in Kontakt kommen. Encoding-Probleme haben ein sehr hohes Frustrationspotential - lassen Sie sich davon nicht unterkriegen.
11.2 Auf Features herunterbrechen & Zahlen, Satzzeichen, etc. entfernen
Sie haben bereits gelernt, wie Sie Texte auf einzelne Features herunterbrechen. Diese tokenization können wir über den tokens-Befehl vornehmen.
Was ich Ihnen verschwiegen habe: Im gleichen Schritt können Sie auch wenig informative Feature entfernen. Dazu gehören z.B. Nummern und Satzzeichen, aber z.B. auch URLs, die sich ggf. im Text befinden. Wir entfernen jetzt Zahlen, Satzzeichen & URLs, weil wir davon ausgehen, dass uns diese Features nicht helfen, Gemeinsamkeiten und Unterschiede zwischen Texten zu bestimmen.
Wichtig: Wie bereits in der Theorie-Sitzung zum Thema erwähnt, kann diese Art der Bereinigung einen grossen Einfluss auf Ihre Ergebnisse haben. Z.B. kann es sein, dass Nummern für manche Analysen durchaus eine Bedeutung haben und die Ergebnisse ganz anders sind, wenn Sie diese entfernen. Sie sollten also sorgfältig überlegen, welche Features Sie warum entfernen.
Ein guter Text, der die Bedeutung der Textbereinigung zusammenfasst und kritisch analysiert, welchen Einfluss die Bereinigung auf ihre Ergebnisse haben, ist hier zu finden:
- Denny, M.J., & Spirling, A. (2018). Text preprocessing for unsupervised learning: why it matters, why it misleads, and what to do about it. Political Analysis, 26(2), 168–189. Link
Wir nehmen die tokenization und die Entfernung von Satzzeichen, Nummern und URLs jetzt in einem Schritt vor. Sie können mit der tokens-Funktion noch mehr Bereinigungsschritte durchführen - das kommt ganz darauf an, welche Features Sie für wenig informativ halten.
Wir verwandeln unsere eingelesenen Text-Files jetzt nicht erst in ein corpus-Objekt und speichern zugehörige document-level-Variablen ab, wie wir es in Tutorial 9 gemacht haben, sondern wandeln den Text direkt aus unserem dataframe-Objekt data zu einem tokens-Objekt um.
tokens <- tokens(data$text, what = "word",
remove_punct = TRUE,
remove_numbers = TRUE,
remove_url = TRUE)
tokens[1]
## Tokens consisting of 1 document.
## text1 :
## [1] "support" "for" "ukip" "continues" "to" "grow" "in" "the"
## [9] "labour" "heartlands" "and" "miliband"
## [ ... and 422 more ]
11.3 Gross- und Kleinschreibung anpassen
In vielen Fällen wollen Sie zudem Ihre Texte weiter normalisieren, d.h. in eine einheitliche Form bringen, indem Sie sämtlichen Text in Kleinschreibung unwandeln. Das hat den Vorteil, dass R ähnliche Wörter in späteren Analyseschritten einfacher als Wörter mit ähnlicher Bedeutung erkennt.
Schauen Sie sich zwei Sätze als Beispiel an:
sentence <- c("Hier würde man das Wort auf die eine Art deuten", "Das Wort hier würde man auf die andere Art deuten")
tokens(sentence)
## Tokens consisting of 2 documents.
## text1 :
## [1] "Hier" "würde" "man" "das" "Wort" "auf" "die" "eine" "Art" "deuten"
##
## text2 :
## [1] "Das" "Wort" "hier" "würde" "man" "auf" "die" "andere" "Art" "deuten"
In dem einen Satz wird das Wort hier grossgeschrieben, weil es zu Beginn des Satzes vorkommt. In dem anderen Satz wird es kleingeschrieben, wie sonst üblich. An beiden Stellen meint das Wort das gleiche - R würde das allerdings nicht erkennen, weil die pattern Hier und hier sich unterscheiden. Hier würde es also helfen, beide Sätze durch Kleinschreibung zu normalisieren.
Unsere Texte sind, wie bereits erwähnt, bereits alle in Kleinbuchstaben vorhanden. Der Vollständigkeit halber zeige ich Ihnen aber noch einmal, wie Sie diese Umwandlung vornehmen können:
## Tokens consisting of 1 document.
## text1 :
## [1] "support" "for" "ukip" "continues" "to" "grow" "in" "the"
## [9] "labour" "heartlands" "and" "miliband"
## [ ... and 422 more ]
11.4 Stopwörter entfernen
Zudem hatten wir ja bereits gesehen, dass unser Korpus viele Stopwörter enthält.
Stopwörter sind Wörter, die wenig informativ sind, wenn wir Gemeinsamkeiten und Unterschiede zwischen Texten analysieren wollen. Das quanteda-Package enthält eine Reihe von Listen mit klassischen Stopwörtern, auch für englischsprachige Texte (denn natürlich unterscheiden sich Stopwörter je nach Sprache).
Sie können sich hier ausgeben lassen, welche Stopwärter im quanteda-Package enthalten sind. Wir lassen uns hier einen Auszug ausgeben.
## [1] "i" "me" "my" "myself" "we" "our" "ours" "ourselves"
## [9] "you" "your" "yours" "yourself" "yourselves" "he" "him" "his"
## [17] "himself" "she" "her" "hers"
Wenn Sie den folgenden Befehl durchführen, sehen Sie, dass diese Stopwörter - etwa Wörter wie for und to entfernt wurden.
## Tokens consisting of 1 document.
## text1 :
## [1] "support" "ukip" "continues" "grow" "labour" "heartlands" "miliband" "scared"
## [9] "leo" "mckinstry" "words" "april"
## [ ... and 239 more ]
Sie können aber auch individuelle Stopwort-Listen entfernen. Z.B. enthält jeder Artikel als formale Merkmal die Wörteranzahl des jeweiligen Dokuments und daher immer das Wort words, das wir entfernen möchten.
Eine individuelle Stopwortliste - hier nur bestehend aus dem pattern word - würden wir folgendermassen entfernen:
## Tokens consisting of 1 document.
## text1 :
## [1] "support" "ukip" "continues" "grow" "labour" "heartlands" "miliband" "scared"
## [9] "leo" "mckinstry" "april" "expresscouk"
## [ ... and 237 more ]
11.5 Lemmatizing/Stemming
Ein weiterer Bereinigungsschritt, der häufig durchgeführt wird, ist, Wörter auf ihre Grundform (lemmatizing) oder ihren Wortstamm (stemming) zu reduzieren. Wir lernen in diesem Tutorial nur letzteres, d.h. wie Sie Ihren Text “stemmen” können.
Zum Beispiel könnte es sein, dass ein Text die Wörter “decide”, “decision” und “decided” enthält.
Das Problem: Eigentlich beschreiben all diese Wörter das gleiche, nämlich, dass irgendetwas entschieden wird.
Damit R das erkennt, müssen diese Wörter normalisiert werden, indem wir sie auf ihren Wortstamm reduzieren. Dies geschieht mit dem Befehl tokens_wordstem():
## Tokens consisting of 3 documents.
## text1 :
## [1] "decid"
##
## text2 :
## [1] "decis"
##
## text3 :
## [1] "decid"
Wenden wir den Befehl auf unseren Korpus an:
## Tokens consisting of 1 document.
## text1 :
## [1] "support" "ukip" "continues" "grow" "labour" "heartlands" "miliband" "scared"
## [9] "leo" "mckinstry" "april" "expresscouk"
## [ ... and 237 more ]
## Tokens consisting of 1 document.
## text1 :
## [1] "support" "ukip" "continu" "grow" "labour" "heartland" "miliband" "scare"
## [9] "leo" "mckinstri" "april" "expresscouk"
## [ ... and 237 more ]
Sie sehen z.B., dass im ersten Text das Wort “continues” auf den Wortstamm “continu” reduziert wurde.
11.6 Sehr häufige/seltene Wörter entfernen
Zuletzt macht es häufig Sinn, Wörter, die in fast jedem Text oder in fast keinem Text vorkommen, zu entfernen. Denn wenn ein Wort immer oder nie vorkommt, wird dieses Wort uns nicht helfen, zwischen Texten Gemeinsamkeiten oder Unterschiede zu finden.
Die Entfernung häufiger oder seltener Wörter können wir vornehmen, in dem wir unsere tokens zu einer document-feature-Matrix umwandeln.
## Document-feature matrix of: 2,833 documents, 26,542 features (99.4% sparse).
## features
## docs support ukip continu grow labour heartland miliband scare leo mckinstri
## text1 3 9 1 1 10 2 7 1 1 1
## text2 0 1 0 0 0 0 0 0 0 0
## text3 0 1 0 0 0 0 0 0 0 0
## text4 0 0 0 0 0 0 0 0 0 0
## text5 0 1 0 0 0 0 0 0 0 0
## text6 0 0 0 0 0 0 0 0 0 0
## [ reached max_ndoc ... 2,827 more documents, reached max_nfeat ... 26,532 more features ]
Konkret weisen wir R mit dem Befehl dfm_trim() an, alle Features zu entfernen, die in weniger als 0.5% aller Dokumente oder in mehr als 99% aller Dokumente vorkommen. Wir sehen, dass dieses Vorgehen unsere DFM-Matrix deutlich verkleinert: Wir haben viel weniger Features als vorher, die aber - so die Hoffnung - jetzt deutlich informativer sind.
#Entfernung häufiger/seltener Wörter
dfm <- dfm_trim(dfm, min_docfreq = 0.005, max_docfreq = 0.99,
docfreq_type = "prop", verbose = TRUE)
## Removing features occurring:
## - in fewer than 14.165 documents: 22,656
## - in more than 2804.67 documents: 1
## Total features removed: 22,657 (85.4%).
## Document-feature matrix of: 2,833 documents, 3,885 features (96.2% sparse).
## features
## docs support ukip continu grow labour heartland miliband scare leo mckinstri
## text1 3 9 1 1 10 2 7 1 1 1
## text2 0 1 0 0 0 0 0 0 0 0
## text3 0 1 0 0 0 0 0 0 0 0
## text4 0 0 0 0 0 0 0 0 0 0
## text5 0 1 0 0 0 0 0 0 0 0
## text6 0 0 0 0 0 0 0 0 0 0
## [ reached max_ndoc ... 2,827 more documents, reached max_nfeat ... 3,875 more features ]
Wir sehen, dass vor allem Features, die in nur sehr wenigen Dokumenten vorkamen, entfernt wurden. Mehr als 22,000 Features wurden insgesamt entfernt und nur rund 4,000 Features bleiben als “informative” Features zurück.
Das waren die wichtigsten Schritte der Bereinigung von Text - auch wenn es noch viele weitere Verfahren gibt.
11.7 Take Aways
Vokabular:
- Preprocessing: Preprocessing bezeichnet die Bereinigung von Text, speziell die Entfernung von Informationen, die uns nicht helfen, Gemeinsamkeiten und Unterschiede zwischen Texten zu analysieren.
- Normalisierung: Normalisierung von Text ist Teil des Preprocessing und bedeutet, dass wir Features angleichen, um sie miteinander vergleichen zu können - etwa, indem wir sie einheitlich in Kleinschreibung umwandeln oder auf ihren Wortstamm zurückführen.
- Lemmatizing: Lemmatizing bezeichnet die Reduzierung eines Wortes auf seine Grundform
- Stemming: Stemming bezeichnet die Entfernung von Suffixen und damit die Reduzierung eines Wortes auf seinen Wortstamm
- relative pruning: Relative pruning bedeutet die Entfernung sehr häufiger/seltener Wörter gemessen daran, in wie viel Prozent aller Dokumente diese vorkommen
Befehle:
- Entfernung von Satzzeichen, Nummern etc.: tokens()
- Kleinschreibung: tokens_tolower()
- Entfernung von Stopwörtern: tokens_remove(), stopwords(“english”)
- Stemming: tokens_wordstem()
- Entfernung häufiger/seltener Wörter: dfm_trim()
- Zählen von Strings: str_count()
11.8 Weitere Tutorials zu diesen Schritten
Sind Fragen offen geblieben? Folgende Tutorials & Paper helfen zu den hier genannten Schritten weiter:
11.9 Übungsaufgabe
Wir arbeiten für die Aufgabe mit einem neuen Textkorpus: speeches.rda
Sie finden den Korpus in OLAT (via: Materialien / Datensätze für R) mit dem Namen speeches.rda.
Bei diesen Dateien handelt es sich um “State of the Union Addresses” von US-Präsidenten seit 1790, die bereits als R-Environment abgespeichert wurde. Die Daten befinden sich in einem ähnlichen Format, wie Sie sie erhalten würden, wenn Sie ihre Text mit dem readtext-Package einlesen würden und zusätzlich noch einige document_level-Variablen bereits abgespeichert hätten.
Quelle der Daten: The American Presidency Project. Geladen via dem Quanteda-Corpus_Package.
11.9.1 Aufgabe 11.1
Wandeln Sie das dataframe-Objekt data in ein corpus-Objekt um, das den Präsidenten, der eine Rede gehalten hat, als auch den Monat der Rede als document-level-Variable enthält.
Brechen Sie den Korpus dann durch Tokenization auf Features herunter, wobei Sie den Text nicht auf einzelne Wörter als Features, sondern auf einzelne Sätze als Analyseeinheit herunterbrechen sollen. (Tipp: Schauen Sie sich den corpus_reshape-Befehl an)
Speichern Sie das Resultat in einem corpus-Objekt mit dem Namen data_sentences ab.
11.9.2 Aufgabe 11.1
Entfernen Sie alle Satzzeichen & Nummern aus dem corpus-Objekt data_sentences, wandeln Sie den Text in Kleinschreibung um und entfernen Sie die von quanteda-vorgegebene Liste von English-language-Stopwörtern.
11.9.3 Aufgabe 11.3
Können Sie herausfinden, wie viele Sätze Ihr Korpus insgesamt enthält?
Die Lösungen finden Sie bei Lösungen zu Tutorial 11.
Wir machen weiter: mit Tutorial 12: Deskriptive Statistik & Diktionaere.