Wie bereits angekündigt, schließt sich dieses Kapitel deshalb nahtlos and das vorausgehende Kapitel 3 zur Sentimentanalyse an, weil wir im Prinzip nichts neues dazulernen müssen, jedenfalls nicht, was die Funktionalität von quanteda angeht. Mit der Funktion dictionary und dem Wissen darüber, wie wir ein Lexikon auf eine DFM anwenden, schlagen wir gewissenmaßen zwei Fliegen mit einer Klappe, denn die Logik hinter den Themen– oder Politikfeld–Lexika, die in diesem Kapitel zur Anwendung kommen, gleicht der Logik von Sentiment-Lexika. Der wichtigste Unterschied besteht darin, dass die in diesem Kapitel behandelten Lexika i.d.R. eine ganze Reihe von Kategorien kennen, nicht nur die Typen positiv und negativ.
Auch hier arbeiten wir wieder mit einer Reihe ganz unterschiedlicher Lexika und Korpora. Hier ein Überblick über die Lexika (die Korpora werden im Überblick genauer beschrieben), von denen alle bis auf das LIWC-Lexikon englischsprachig sind:
Wie die Namen und eine Lektüre der Datensatzdokumentationen verrät, bilden diesen Lexika unterschiedliche Themenbereiche und Politikfelder ab. Dieser Ansatz ist vielsprechender, als es möglicherweise zunächst erscheint: Makroökonomie oder Ortsnamen sind sehr kontrollierte semantische Bereiche, die sich relativ präzise und erschöpfend mit einem Lexikon abbilden lassen.
Wir starten mit dem EU–Speech–Korpus, an dem wir gleich mehrere politische Lexika ausprobieren, und machen dann weiter mit dem UN–Generaldebattenkorpus, welches wir anhand unterschiedlicher Policy–Lexika untersuchen. Schließlich analysieren wir deutschsprachige Facebook–Kommentare, auf die wir die deutsche Fassung des LIWC–Lexikons anwenden, vermutlich eines der umfangreichsten Inhaltsanalyse–Lexikon überhaupt.
Wieder werden zunächst die notwendigen Bibliotheken geladen.
if(!require("quanteda")) {install.packages("quanteda"); library("quanteda")}
if(!require("readtext")) {install.packages("readtext"); library("readtext")}
if(!require("tidyverse")) {install.packages("tidyverse"); library("tidyverse")}
if(!require("lubridate")) {install.packages("lubridate"); library("lubridate")}
theme_set(theme_minimal())
Anschließend wird das umfangreiche EU Speech-Korpus eingelesen, auf das sich die vorwiegend politischen Lexika auf unserer Liste gut anwenden lassen.
load("daten/euspeech/euspeech.korpus.RData")
Ein Blick in die Metadaten gibt uns einen Eindruck der Korpus-Zusammensetzung. Im EU Speech-Korpus sind Reden von hochrangigen Vetretern der EU-Mitgliedstaaten gespeichert, dazu Reden von Mitgliedern des Europaparlaments und von Vetretern der EU-Kommission und der EZB. Alle Reden sind in englischer Sprache gespeichert, zum Teil als Übersetzung. Zudem gibt es noch Metadaten zu Sprechern, Länge und Anlass der Reden. Die Variable Sentences ist hier nicht aussagekräftig, weil das Korpus bereits vor dem Einlesen in R in tokenisierter Form vorlag.
head(korpus.euspeech.stats)
Im vorausgehenden Kapitel haben wir eingangs ein sehr simples Lexikon definiert, um die Struktur dieses Objekts in quanteda zu erläutern. Wir haben dieses Lexikon allerdings nicht angewandt, sondern sind gleich dazu übergangen, ein bereits fertiges Sentimentlexikon zu laden. Jetzt ist es an der Zeit, ein wenig mehr Arbeit in ein Ad hoc-Lexikon zu investieren, und dabei die Tatsache auszunutzen, dass ein quanteda-dictionary aus einer Reihe von Begriffen zu ganz unterschiedlichen Kategorien bestehen kann. Die im folgenden Beispiel für die Kategorie Populismus verwendete Wortliste stammt aus Rooduijn und Pauwels (2011) und wird in dieser Übung von Ken Benoit (dem Erfinder von quanteda) herangezogen, während wir die zweite Wortliste zur Kategorie Liberalismus ad hoc selbst zusammengestellt haben.
populismus.liberalismus.lexikon <- dictionary(
list(
populism = c("elit*", "consensus*", "undemocratic*", "referend*", "corrupt*", "propagand", "politici*", "*deceit*", "*deceiv*", "*betray*", "shame*", "scandal*", "truth*", "dishonest*", "establishm*", "ruling*"),
liberalism = c("liber*", "free*", "indiv*", "open*", "law*", "rules", "order", "rights", "trade", "global", "inter*", "trans*", "minori*", "exchange", "market*")))
populismus.liberalismus.lexikon
Dictionary object with 2 key entries.
- [populism]:
- elit*, consensus*, undemocratic*, referend*, corrupt*, propagand, politici*, *deceit*, *deceiv*, *betray*, shame*, scandal*, truth*, dishonest*, establishm*, ruling*
- [liberalism]:
- liber*, free*, indiv*, open*, law*, rules, order, rights, trade, global, inter*, trans*, minori*, exchange, market*
Wie zuvor wenden wir das Lexikon auf unsere Daten an, dabei gruppieren wir hier zunächst nach der Variable country (die EU-Kommission, das EU-Parlament, sowie die EZB werden hier der Einfachheit halber auch als “countries” behandelt).
meine.dfm.eu <- dfm(korpus.euspeech, groups = "country", dictionary = populismus.liberalismus.lexikon)
meine.dfm.eu.prop <- dfm_weight(meine.dfm.eu, scheme = "prop")
convert(meine.dfm.eu.prop, "data.frame")
Ein Plot sparen wir uns an dieser Stelle. Es ist auch so sofort offensichtlich, dass Treffer auf die Kategorie Liberalismus im Vergleich klar gegenüber der Kategorie Populismus überwiegen, was angesichts der Zusammensetzung der Daten kaum überrascht. Allerdings scheinen die Unterschiede zwischen den beiden EU-Behörden Kommission und EZB, gefolgt von Deutschland, den Niederlanden und Tschechien einerseits, und Griechenland, Spanien und dem EU-Parlament andererseits den (sehr hemdsärmeligen) Kontrast, den unser Lexikons unterstellt, grundsätzlich zu bestätigen. Ist die erste Gruppe sehr wenig populistisch (jedenfalls nach Definition dieses simplen Lexikons), sieht das für die zweite Gruppe bereits etwas anders aus.
Als nächstes berechnen wir den relativen Populismus-Anteil nach Jahren im Zeitrum von 2007-2015, und unterscheiden dabei zwischen zwei Typen von Akteuren: nationalen Regierungen (hier inkl. EU-Parlament) einerseits und EU-Behörden (EU–Kommission und EZB) andererseits.
meine.dfm.eu <- dfm(korpus.euspeech, groups = c("Typ", "Jahr"), dictionary = populismus.liberalismus.lexikon)
meine.dfm.eu.prop <- dfm_weight(meine.dfm.eu, scheme = "prop")
eu.themen <- convert(meine.dfm.eu.prop, "data.frame") %>%
mutate(Typ = str_split(doc_id, "\\.", simplify = T)[,1]) %>%
mutate(Jahr = str_split(doc_id, "\\.", simplify = T)[,2]) %>%
select(Typ, Jahr, populism, liberalism)
eu.themen
Auch hier überspringen wir das Plot. Während der Populismus-Anteil bei den EU–Behörden relativ konstant bei circa 2% liegt, ist er bei den Vertretern der EU-Mitgliedstaaten mit 7-9% deutlich höher.
Bevor wir uns im nächsten Schritt “richtigen” Lexika zuwenden, die deutlich umfangreicher sind als unser Populismus–Lexikon, betrachten wir noch kurz die Variation innerhalb der Reden, da diese relativ lang sind, und sich so auch eine Themenverteilung berechnen lässt, ohne dass man mithilfe von group die DFM nach einer bestimmten Variable zusammenfasst. Das folgende Plot (welches Boxplot und Scatterplot kombiniert) zeigt die Variation beim Populismus-Anteil nach Land innerhalb einzelner Reden.
meine.dfm.eu <- dfm(korpus.euspeech, dictionary = populismus.liberalismus.lexikon)
meine.dfm.eu.prop <- dfm_weight(meine.dfm.eu, scheme = "prop")
eu.poplib <- convert(meine.dfm.eu.prop, "data.frame") %>%
bind_cols(korpus.euspeech.stats) %>%
filter(length >= 1200, populism > 0 | liberalism > 0)
ggplot(eu.poplib, aes(country, populism)) +
geom_boxplot(outlier.size = 0) +
geom_jitter(aes(country,populism), position = position_jitter(width = 0.4, height = 0), alpha = 0.1, size = 0.2, show.legend = F) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1)) +
xlab("") + ylab("Populismus-Anteil (%)") +
ggtitle("Populismus-Anteil in Reden des EU-Speech-Korpus")
Wie die Dichte der Punkte im Plot zeigt, ist der Anteil der Kommission am Korpus insgesamt groß, auch wenn das Populismus-Niveau (gemessen mit unserem primitiven Lexikon) insgesamt niedrig ist. Desweiteren erkennen wir eine größere Bandbreite bei den nationalen Regierungen (Griechenland vs. Deutschland) als bei den Behörden.
Hier sollte man Ausreißer (etwa bei der EU-Kommision) mit einem Populismus-Anteil von 100% nicht überschätzen. Zwar haben wir zuvor sehr kurze Reden ausgeschlossen (mit length >= 1200), aber es dürfte trotzdem solche Texte geben, die nur durch eine handvoll Treffer auf eine der beiden Kateogorien ein solches Ergebnis erreichen (dies gielt auch für einen Liberalismus–Anteil von 100%). Wie auch beim Sentiment gilt: Es handelt sich um heuristische Verfahren, und mit einem besseren Lexikon lässt sich die Genauigkeit leicht steigern.
Es muss betont werden, dass unser Ad-hoc-Lexikon sicherlich nicht ausreicht, um den Stil oder das Themenspektrum politischer Debatten adäquat anzubilden. Wir erstellen deshalb jetzt zwei quanteda-Lexika auf Grundlage des Policy Agenda–Lexikons und auf Basis des Laver Garry-Lexikons. Bei beiden Lexika handelt es sich um in der Poltikwissenschaft vielfach eingesetzte Resourcen für die Bestimmung von Politikfeldern. Die Lexikon–Daten dazu kommen für Policy Agendas aus einer schon vorbereiteten RData-Datei und für Laver Garry aus einer Textdatei im Format “WordStat”, welches der dictionary-Befehl bereits kennt.
Wir schauen in beide Lexika mit dem Befehl head hinein – man erkennt gleich den großen Umfang.
load("lexika/policy_agendas_english.RData")
policyagendas.lexikon <- dictionary(dictLexic2Topics)
lavergarry.lexikon <- dictionary(file = "lexika/LaverGarry.cat", format = "wordstat")
head(policyagendas.lexikon, 2)
Dictionary object with 2 key entries.
- [macroeconomics]:
- aggregate demand, aggregate supply, business cycle, demand shock, demand side, demand-side, econom, employment rate, full employment, food price, industr, keynes, bank of canada, bank of england, bear market, bretton woods, budget, bull market, changing demographic, coinage [ ... and 62 more ]
- [civil_rights]:
- civil right, ableism, abortion, access to info, african american, anti-choice, anti-semit, bill 101, charter of the french language, biphobi, bisexual, charter of rights, civil libert, disabilit, discriminat, diversity, equal employm, equal opportunit, equal right, equalit [ ... and 65 more ]
head(lavergarry.lexikon, 2)
Dictionary object with 2 primary key entries and 2 nested levels.
- [CULTURE]:
- people, war_in_iraq, civil_war
- [CULTURE-HIGH]:
- art, artistic, dance, galler*, museum*, music*, opera*, theatre*
- [CULTURE-POPULAR]:
- media
- [SPORT]:
- angler*
- [ECONOMY]:
- [+STATE+]:
- accommodation, age, ambulance, assist, benefit, care, carer*, child*, class, classes, clinics, collective*, contribution*, cooperative*, co-operative*, deprivation, disabilities, disadvantaged, educat*, elderly [ ... and 30 more ]
- [=STATE=]:
- accountant, accounting, accounts, advert*, airline*, airport*, audit*, bank*, bargaining, breadwinner*, budget*, buy*, cartel*, cash*, charge*, commerce*, compensat*, consum*, cost*, credit* [ ... and 51 more ]
- [-STATE-]:
- assets, autonomy, barrier*, bid, bidders, bidding, burden*, charit*, choice*, compet*, confidence, confiscatory, constrain*, contracting*, contractor*, controlled, controlling, controls, corporate, corporation* [ ... and 42 more ]
Die beiden Lexika zeichnen sich dafurch aus, dass sie geschachtelte Kategorien aufweisen, unter denen sich eine Reihe von Unterkategorien verbergen.
Nun berechnen wir je eine DFM für jedes Lexikon und gruppieren diese einmal nach Land und einmal nach Jahr.
meine.dfm.eu.pa <- dfm(korpus.euspeech, groups = "country", dictionary = policyagendas.lexikon)
meine.dfm.eu.lg <- dfm(korpus.euspeech, groups = "Jahr", dictionary = lavergarry.lexikon)
Das folgende Plot zeigt die Themenverteilung innerhalb des Korpus nach Land, auf Basis des Policy Agenda-Lexikons. Wir haben zuvor einige Themen ausgewählt und auf der Grundlage dieser Auswahl die Anteile berechnet. Andere Bereiche haben wir unter other zusammengefasst.
eu.themen.pa <- convert(meine.dfm.eu.pa, "data.frame") %>%
rename(Land = doc_id) %>%
select(Land, macroeconomics, finance, foreign_trade, labour, healthcare, immigration, education, intl_affairs, defence) %>%
gather(macroeconomics:defence, key = "Thema", value = "Anteil") %>%
group_by(Land) %>%
mutate(Anteil = Anteil/sum(Anteil)) %>%
mutate(Thema = as_factor(Thema))
ggplot(eu.themen.pa, aes(Land, Anteil, colour = Thema, fill = Thema)) +
geom_bar(stat="identity") +
scale_colour_brewer(palette = "Set1") +
scale_fill_brewer(palette = "Pastel1") +
ggtitle("Themen im EU-Speech-Korpus auf Basis des Policy Agendas-Lexikons") +
xlab("") + ylab("Themen-Anteil (%)") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
Die Anteilsverteilung dürfte in Teilen kaum überraschen. Die EZB und in einem etwas geringerem Umfang auch die Kommission beschäftigen sich viel mit Makroökonomie und Finanzen, Frankreich und Spanien mit dem Arbeitsmarkt, und Deutschland und die Niederlande viel mit Handel. Interessant sind die Themenfelder Verteididung (Niederlande, Frankreich, Großbritannien), Zuwanderung (Tschechien) und Bildung (Frankreich, Großbritannien). Die geringe Relevanz des Zuwanderungsthemas in Italien und Griechenland belegt vielleicht eine Diskrepanz zwischen öffentlicher Meinung und der Regierungsagenda –– jedenfalls im Zeitraum 2007–2015.
Wir wenden uns nun dem Laver-Garry-Lexikon zu, welches wir gezielt auf den Zeitverlauf 2007-2015 anwenden, um Veränderungen in den Blick zunehmen. Die vielen Umformungsschritte werden notwendig, weil das Lexikon stark geschachtelte Kategorien besitzt, die wir zur besseren Übersichtlichkeit zum Teil zusammenfassen und umbennnen.
eu.themen.lg <- dfm_weight(meine.dfm.eu.lg, scheme = "prop") %>%
convert("data.frame") %>%
rename(Jahr = doc_id) %>%
mutate(culture = `CULTURE` + `CULTURE.CULTURE-HIGH` + `CULTURE.CULTURE-POPULAR` + `CULTURE.SPORT`) %>%
mutate(economy = `ECONOMY.+STATE+` + `ECONOMY.=STATE=` + `ECONOMY.-STATE-`) %>%
mutate(environment = `ENVIRONMENT.CON ENVIRONMENT` + `ENVIRONMENT.PRO ENVIRONMENT`) %>%
mutate(institutions = `INSTITUTIONS.CONSERVATIVE` + `INSTITUTIONS.NEUTRAL` + `INSTITUTIONS.RADICAL`) %>%
mutate(values = `VALUES.CONSERVATIVE` + `VALUES.LIBERAL`) %>%
mutate(law_and_order = `LAW_AND_ORDER.LAW-CONSERVATIVE`) %>%
mutate(rural = `RURAL`) %>%
mutate(other = `GROUPS.ETHNIC` + `GROUPS.WOMEN` + `LAW_AND_ORDER.LAW-LIBERAL` + `URBAN`) %>%
select(Jahr, culture:other) %>%
gather(Thema, Prozent, culture:other) %>%
filter(!Thema %in% c("economy", "institutions", "values", "culture", "rural", "other"))
ggplot(eu.themen.lg, aes(Jahr, Prozent, group = Thema, col = Thema)) +
geom_line(size = 1) +
scale_colour_brewer(palette = "Set1") +
ggtitle("Themen im EU-Speech-Korpus auf Grundlage des Laver-Garry-Lexikons") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
xlab("") + ylab("Themen-Anteil (%)")