Sitzung 5: Qualitätskriterien

Author

Valerie Hase & Luisa Kutlar

1. Pakete laden und Daten einlesen

Zunächst installieren wir alle Pakete, die wir für diese Sitzung brauchten (z.B. tidyverse). Ihr braucht install.packages() nur, wenn ihr die Pakete im Methodencafe noch nicht installiert hattet.

#install.packages("tidyverse)
#install.packages("quanteda")
#install.packages("RCurl")
#install.packages("caret")

library("tidyverse")
library("quanteda")
library("RCurl")
library("caret")

Wir nutzen hier wieder eine bestehende Pipeline, nämlich aus Sitzung 1 und 3.

Als erstes laden wir unseren Serien-Datensatz ein & bereinigen diesen, genau wie wir es in Sitzung 1 gelernt haben:

# Daten laden
url <-  getURL("https://raw.githubusercontent.com/valeriehase/textasdata-ms/main/data/data_tvseries.csv")
data <- read.csv2(text = url)

# Preprocessing
tokens <- tokens(data$Description,
                 what = "word", #Tokenisierung, hier zu Wörtern als Analyseeinheit
                 remove_punct = TRUE, #Entfernung von Satzzeichen
                 remove_numbers = TRUE) %>% #Entfernung von Zahlen
  
  # Kleinschreibung
  tokens_tolower()

# Text-as-Data Repräsentation als Document-Feature-Matrix
dfm <- tokens %>% 
  dfm() 

Nehmen wir an, dass wir - wie in Sitzung 3- mittels eines organischen Diktionärs analysieren wollen, ob es sich bei Serien um Serien mit Kriminalbezug handelt oder nicht:

Dafür erstellen wir zunächst mit dictionary() aus dem quanteda-Paket eine eigene Wortliste, um Serien zu identifizieren, die sich mit “Crime” beschäftigen:

diktionär_crime <- dictionary(list(crime = c("crim*", "police*", "gun*", 
                                             "shot*", "dead*", "murder*", 
                                             "kill*", "court*", "suspect*", 
                                             "witness*", "arrest*", "officer*", 
                                             "verdict*")))

Genau wie in Sitzung 3 klassifizieren wir also Serien als “Krimi” oder “kein Krimi”:

#Diktionär anwenden
crime_tvshows <- dfm %>% 
  dfm_weight(scheme = "prop") %>% 
  dfm_lookup(dictionary = diktionär_crime)

# Ergebnis für die weitere Analyse in einen Data Frame umwandeln
crime_tvshows <- convert(crime_tvshows, 
                         to = "data.frame") %>%
  
  # Umwandlung in tibble-Format
  as_tibble %>%
  
  # Wir ergänzen zunächst wieder die Serientitel
  mutate(Title = data$Title) %>%
  
  # Wir erstellen eine Variable, die Texte als
  # "1" (Krimi) oder "0" (kein Krimi) identifiziert
  mutate(crime_binary = 1,
         crime_binary = replace(crime_binary,
                         crime == 0,
                         0)) %>%

# Sortierung der Variablen
select(Title, crime, crime_binary)

#Ausgabe der Ergebnisse
head(crime_tvshows)
# A tibble: 6 × 3
  Title               crime crime_binary
  <chr>               <dbl>        <dbl>
1 1. Game of Thrones      0            0
2 2. Breaking Bad         0            0
3 3. Stranger Things      0            0
4 4. Friends              0            0
5 5. The Walking Dead     0            0
6 6. Sherlock             0            0

Schauen wir uns nochmal an, wie viel Prozent aller Serien als Krimis identifiziert wurden:

#Ausgabe der Crime vs. Non-Crime Serien
crime_tvshows %>%
  
  # absolute Anzahl jeder Sentiment-Art (n)
  count(crime_binary) %>%
  
  # Ausgabe in Prozent (perc)
  mutate(perc = prop.table(n)*100,
         perc = round(perc, 2))
# A tibble: 2 × 3
  crime_binary     n  perc
         <dbl> <int> <dbl>
1            0   730  81.1
2            1   170  18.9

Können wir diesen Zahlen trauen - z.B., wenn wir diese deskriptiven Ergebnisse berichten oder die Klassifizierung als Variable für eine weitere, inferenzstatistische Analyse nutzen wollten? Das testen wir jetzt - auf Basis unserer Qualitätskriterien.

2. Validierung automatisierter Analysen

Wir haben im Workshop bereits gelernt, warum die Validierung automatisierter Analysen wichtig ist: Wir sollten den Ergebnissen automatisierter Inhaltsanalysen nicht blind vertrauen, weil oft unklar ist, inwiefern automatisiert gemessen wird/werden kann, was uns theoretisch interessiert.

Daher empfehlen Grimmer und Stewart (2013, S. 271): “Validate, Validate, Validate. […] What should be avoided, […] is the blind use of any method without a validation step.”

Wir können uns bei der Auswertung und Interpretation unserer Ergebnisse inbesondere folgende Fragen stellen:

  • Inwiefern kann und sollte ich theoretische Konstrukte, die für meine Studie von Interesse sind, überhaupt automatisiert messen?
  • Wie sehr überlappt die automatisierte Analyse mit einer manuellen Codierung der gleichen Variable(n) - und wo finden sich Unterschiede?
  • Wie lassen sich diese Unterschiede erklären, d.h., inwiefern messen manuelle und automatisierte Codierungen ggf. unterschiedliche Dinge und wieso?

Wir arbeiten hier nur mit einer Variante der Validierung: dem Vergleich mit einem manuellen “Goldstandard”.

Im Prinzip werden bei dieser Form der Validierung die automatisierte und manuelle Codierung der gleichen Variablen für die gleichen Texte verglichen. Oft wird dabei die manuelle Codierung als “Goldstandard” bezeichnet, d.h. es wird impliziert, dass manuelle Analysen in der Lage sind, den “wahren” Wert von Variablen in Texten zu erfassen.

Inwiefern manuelle Codierungen (oder jegliche Form von Codierungen) dazu in der Lage sind, lässt sich natürlich hinterfragen, wie di Maggio (2013, S. 3f.) und Song et al. (2020, S. 553ff.) kritisch zusammenfassen. Z.B. unterscheiden sich manuelle Codierer:innen oft in Ihren Codierungen; zudem lässt sich aus erkenntnistheoretischer Perspektive diskutieren, inwiefern der “wahre” Wert von Variablen überhaupt messbar ist.

Was sich anhand der Validierung durch Vergleich zu einer manuellen Codierung in jedem Fall zeigen lässt, ist, inwiefern sich automatisierte und manuelle Codierung unterscheiden - und wieso dies der Fall sein könnte. Entsprechend können wir durch eine Validierung in jedem Fall besser verstehen, welche (theoretischen) Konstrukte wir mit der automatisierten Analyse messen (können).

2.1 Manuelle Annotation: Goldstandard erstellen

Zuerst würden wir nun den manuellen Goldstandard erstellen. Wir codieren also:

  • Werden Serien als Krimis beschrieben? (Code: 1)
  • Werden Serien nicht als Krimis beschrieben? (Code: 0)

Nun wollen wir als nächstes schauen, inwiefern die automatisierte Messung von “Krimi” mit der manuellen Codierung, d.h. dem “Goldstandard”, übereinstimmt.

Dafür ziehen wir als erstes eine zufällige Stichprobe aus Ihrem Korpus, die manuell validiert werden soll.

Song et al. (2020, S. 564) empfehlen, für die Validierung automatisierter Inhaltsanalysen möglichst mehr als 1.300 Artikel manuell zur Erstellung eines “Goldstandards” zu codieren. Dabei sollte die manuelle Codierung von mehr als eine/r Codierer:in durchgeführt werden und die Intercoderreliabilität mindestens .7 betragen - im Rahmen dieses Workshops ist das natürlich nicht möglich.

Hier beschränken wir uns aus praktischen Gründen auf eine kleinere Stichprobe, hier als Beispiel 30 Artikel, und die Codierung durch eine Codierer:in.

Zunächst ziehen wir eine zufällige Stichprobe für die manuelle Validierung. Mit der Funktion slice_sample() können wir dabei z.B. aus allen Texten des Korpus, hier den Zeilen des Dataframes data, 30 Texte zufällig auswählen.

Dann fügen wir folgende Informationen hinzu, um ein “Codesheet” zu erstellen:

  • Die Variable “ID” enthält die ID der manuell zu codierenden Texte.
  • Die Variable “Description” enthält die zu manuell codierenden Texte.
  • Die Variable “Manual.Coding” enthält leere Zellen, in die wir unsere Codierung eintragen können.
sample <- data %>%
  
  #Erstellung der Variable ID
  mutate(ID = paste0("ID", 1:nrow(data))) %>%
  
  # Stichprobe ziehen
  slice_sample(n = 30) %>%
  
  # Variable Manual Coding hinzufügen
  mutate(Manual.Coding = NA) %>%
  
  # Reduktion auf die drei relevanten Variablen
  select(ID, Description, Manual.Coding)

Jetzt schreiben wir diese Datei mit write.csv() auf unseren lokalen Speicher.

write.csv2(sample, "validation_dictionary.csv")

Jetzt würden wir ausserhalb von R die Validierung vornehmen. D.h., wir lesen jeden Text und tragen dann in der jeweiligen Zeile der Spalte Manual.Coding die manuelle Codierung ein:

  • eine 1, wenn die Serienbeschreibung zeigt, dass es sich dabei um einen Krimi handelt (1 = “Krimi”)
  • eine 0, wenn die Serienbeschreibung zeigt, dass es sich dabei nicht um einen Krimi handelt (0 = “kein Krimi”)

Dabei sollten Sie sich wie bei jeder manuellen Inhaltsanalyse an ein klares Codebuch mit Codieranweisungen halten.

Jetzt speichert die Ergebnisse in einem neuen Excel-Sheet mit Titel validation_dictionary_coded und lest den Goldstandard mit read.csv2() wieder ein:

sample_coded <- read.csv2("validation_dictionary_coded.csv")

Nun fügen wir die automatisierte und die manuelle Analyse der gleichen Texte über die ID der jeweiligen Texte in einem Dataframe mit dem Namen confusion zusammen. Dafür nutzen wir den merge()-Befehl.

Wieso das Objekt confusion heisst, werdet ihr gleich verstehen.

confusion <- crime_tvshows %>%
  
  # Erstellung der ID Variable für das Matching
  mutate(ID = paste0("ID", 1:nrow(data))) %>%
 
  # Match mit den codierten Daten 
  right_join(sample_coded) %>%
  
  # Reduktion auf die relevanten Variablen
  select(ID, crime_binary, Manual.Coding) %>%
  mutate(crime_binary = as.factor(crime_binary),
         Manual.Coding = as.factor(Manual.Coding)) %>%
  
  # Anpassung der Variablennamen
  rename(automated = crime_binary,
         manual = Manual.Coding) 

#Ausgabe der Ergebnisse
head(confusion)
# A tibble: 6 × 3
  ID    automated manual
  <chr> <fct>     <fct> 
1 ID7   0         0     
2 ID26  0         0     
3 ID55  0         0     
4 ID81  0         0     
5 ID142 0         0     
6 ID153 0         0     

2.2 Validieren

Ihr seht neben der ID des jeweiligen Textes die Klassifikation des Textes auf Basis der automatisierten Codierung, hier in der Spalte automated, und der manuellen Codierung, hier in der Spalte manual.

Wie können wir nun einen Kennwert erhalten, der uns angibt, wie stark die manuelle und die automatisierte Inhaltsanalyse übereinstimmen?

Um dies zu beurteilen, greifen wir auf Precision, Recall und den F1-Wert als gängige Kennwerte zurück, die ihr auf den Folien bereits kennengelernt habt und die in vielen Studien - siehe etwa Nelson et al. (2018) - genutzt werden, um automatisierte und manuelle Codierungen zu vergleichen.

Precision:

Der Kennwert Precision gibt an, wie gut die automatisierte Analyse darin ist, nur Artikel als “Krimi” zu klassifizieren, die laut manuellem Goldstandard tatsächlich Krimis sind.

Precision = \frac{True Positives}{True Positives + False Positives}

Dieser Kennwert reicht von minimal 0 bis maximal 1 und sagt folgendes aus: Wie gut ist die Methode darin, nicht zu viele Serien fälschlicherweise (im Vgl. zum manuellen Goldstandard) als “Krimi” zu klassifizieren, d.h. “False Positives” zu erzeugen? Je näher der Wert bei 1 liegt, desto besser die Analyse.

Recall:

Der Kennwert Recall gibt an, wie gut die automatisierte Analyse darin ist, alle Serien, die laut manuellem Goldstandard tatsächlich Krimis sind, als Krimis zu klassifizieren.

Recall = \frac{True Positives}{True Positives + False Negatives}

Dieser Kennwert reicht ebenfalls von minimal 0 bis maximal 1 und sagt folgendes aus: Wie gut ist die Methode darin, nicht zu viele Serien fälschlicherweise (im Vgl. zum manuellen Goldstandard) als “kein Krimi” zu klassifizieren, d.h. “False Negatives” zu erzeugen? Je näher der Wert bei 1 liegt, desto besser die Analyse.

F1-Wert:

Der F1-Wert ist der harmonische Mittelwert aus beiden Kennwerten. Er wird meist angegeben, wenn man eine übergreifende Metrik benötigt, die Precision und Recall zugleich einbezieht.

F_{1} = 2 * \frac{Precision * Recall}{Precision + Recall}

Nachfolgend lassen wir uns Precision, Recall und den F1-Wert ausgeben.

Dafür benötigen wir das Packet caret und nutzen den Befehl confusionMatrix().

Wichtig ist, dass wir dabei

  • alle Klassifikationsvariablen im Faktor-Format vorliegen müssen.
  • via data R anweisen müssen, in welchem Objekt die automatisierte Codierung zu finden ist.
  • via reference R anweisen müssen, in welchem Objekt der Goldstandard zu finden ist.
  • via mode R anweisen müssen, dass wir Kennwerte wie Precision, Recall etc. erhalten wollen.
  • via positive R anweisen müssen, welcher Wert das Vorkommen der Variable bezeichnet, d.h. hier, dass die Ausprägung “Krimi” im Goldstandard mit einer 1 codiert wurde.
# Berechnung der Validität
confusionMatrix(data = confusion$automated,
                reference = confusion$manual, 
                mode = "prec_recall", 
                positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction  0  1
         0 23  1
         1  2  4
                                          
               Accuracy : 0.9             
                 95% CI : (0.7347, 0.9789)
    No Information Rate : 0.8333          
    P-Value [Acc > NIR] : 0.2396          
                                          
                  Kappa : 0.6667          
                                          
 Mcnemar's Test P-Value : 1.0000          
                                          
              Precision : 0.6667          
                 Recall : 0.8000          
                     F1 : 0.7273          
             Prevalence : 0.1667          
         Detection Rate : 0.1333          
   Detection Prevalence : 0.2000          
      Balanced Accuracy : 0.8600          
                                          
       'Positive' Class : 1               
                                          

Am einfachsten zu interpretieren ist dabei die Konfusions-Matrix, die anzeigt, welche Fälle automatisiert und manuell gleich oder unterschiedlich codiert wurden.

Die Matrix zeigt, wie viele Texte der automatisierten Codierung (“Prediction”), die mit 0 (“kein Krimi”) bzw. 1 (“Krimi”) codiert wurden beim manuellen Goldstandard mit 0 (“kein Krimi”) bzw. 1 (“Krimi”) codiert wurden - und andersherum.

Je mehr Texte bei beiden Codierungen also gleichermassen eine 0 ausweisen oder gleichermassen eine 1 ausweisen, desto besser die Übereinstimmung zwischen automatisierter und manueller Analyse.

Je mehr Texte bei einer der Codierungen aber eine 0 und bei einer anderen eine 1 aufweisen (oder andersherum), desto schlechter die Übereinstimmung zwischen automatisierter und manueller Analyse.

Für die Festlegung “guter” Kennwerte, was Precision, Recall und den F1-Wert angeht, gibt es in der Kommunikationswissenschaft (noch) keine einheitlichen Vorgaben. Ihr könnt euch grob an den Vorgaben zu “guten” Intercoder-Reliabilitätswerten orientieren - z.B. würde ein Wert von .8 für Precision, Recall oder den F1-Wert dafür sprechen, dass die automatisierte Analyse valide Ergebnisse liefert.

Insgesamt würden wir auf Basis dieser Ergebnisse davon ausgehen, dass unser Diktionär nicht so gut funktioniert, wenn es darum geht, möglichst alle negativen Texte zu erkennen, da Recall = .8 aber Precision = .66.

Das könnte z.B. ein Hinweis darauf sein, dass unser Diktionär zu “breit” ist, d.h. zu viele nicht eindeutige “Krimi”-Wörter und damit unpräzise Suchbegriffe enthält.