Kapitel 8 Buchstaben in Romanen
8.1 Packages
library(tidyverse)
library(tidytext)
library(scales)
library(readtext)
library(rmarkdown)
# library(qdap) # syllable_count and syllable_sum
# library(quanteda) # nsyllable(tokens(txt))
Im Wikipedia-Artikel zum Thema Buchstabenhäufigkeit gibt die folgende Tabelle Auskunft über die Häufigkeit von Buchstaben in einer Stichprobe von deutschen Texten. Die Umlaute werden in dieser Tabelle als jeweils zwei Monophthonge gezählt.
library(readxl)
<-
buchstabenhaeufigkeit read_xlsx("data/wikipedia_buchstabenhaeufigkeit_deutsch.xlsx")
%>% rmarkdown::paged_table() buchstabenhaeufigkeit
Die ersten fünf Buchstaben haben einen Anteil von etwa der Hälfte, die häufigsten zehn Buchstaben decken etwa drei Viertel der relativen Buchstabenhäufigkeit in deutschen Texten ab.
Eine weitere Tabelle zeigt die Häufigkeit der Buchstaben in Texten aus einem Briefkorpus (Briefe aus den Jahren 1996-2004). In diesem Fall sind auch die Frequenzen der Umlaute erhoben worden. Die zehn häufigsten Buchstaben im Briefkorpus decken sich zum großen Teil mit denen im vorher gezeigten.
library(readxl)
<-
buchstabenhaeufigkeit_briefe read_xlsx("data/wikipedia_buchstabenhaeufigkeit_briefkorpus.xlsx")
%>% rmarkdown::paged_table() buchstabenhaeufigkeit_briefe
In einem anderen Wikipedia-Artikel mit dem Titel Frekvence črk werden die relativen Häufigkeiten der Buchstaben in slowenischen belletristischen Texten tabellarisch dargestellt und einigen anderen Sprachen gegenübergestellt. In dieser Tabelle fällt auf, dass die Graphme der Vollvokale a und o einen deutliche höheren Rang einnehmen als in den beiden Tabellen für deutsche Texte. Ähnlich wie in den Tabellen für die deutschen Texte ist wiederum, dass die Vokalgrapheme e und i zu den häufigsten gehören. Unter den Konsonantgraphemen sind auch hier n, s, r und t stark vertreten.
library(readxl)
<-
buchstabenhaeufigkeit_slov read_xlsx("data/wikipedia_frekvence_crk.xlsx")
%>% rmarkdown::paged_table() buchstabenhaeufigkeit_slov
Wir stellen uns die Aufgabe, die Buchstabenhäufigkeit in von uns ausgewählten Texten literarischer Prosa tabellarisch zusammenzustellen und mit denen im Wikipedia-Artikel zu vergleichen. In den folgenden Abschnitten beschäftigen wir uns mit der Häufigkeit von Vokalgraphemen, Konsonantengraphemen, Konsonantenverbindungen und Silben in tabellarischer und graphischer Form.
8.2 Datensatz lesen
Die readtext()-Funktion erlaubt Einlesen von mehreren Dateien auf einfache Art und Weise. Mit docvarsfrom erhalten wird eine neue Spalte in der Tabelle, die wir mit der Funktion rename() umbenennen. Mit encoding = “UTF-8” teilen wir dem Programm mit, wie der Text kodiert ist (Code Page).
= readtext("data/books/*.txt",
novels_txt docvarsfrom = "filenames",
encoding = "UTF-8") %>%
rename(title = docvar1)
novels_txt
## readtext object consisting of 2 documents and 1 docvar.
## # Description: df [2 x 3]
## doc_id text title
## <chr> <chr> <chr>
## 1 prozess.txt "\"Der Prozes\"..." prozess
## 2 tom.txt "\"Tom Sawyer\"..." tom
8.3 Buchstaben extrahieren
8.3.1 aus Liste
Der reguläre Ausdruck [a-zA-Z] extrahiert nur Buchstaben des englischen Alphabets, [:alpha:] extrahiert dagegen auch nicht-englische Buchstaben, z.B. deutsche oder slowenische Sonderzeichen. Zahlen und andere spezielle Zeichen (z.B. Interpunktion) werden auf diese Weise nicht extrahiert.
Regex {1} (= default) extrahiert Einzelbuchstaben. Bei Verwendung von {2} werden jeweils zwei aufeinander folgende Buchstaben extrahiert.
Die Funktion tolower() sorgt dafür, dass Großbuchstaben in Kleinbuchstaben umgewandelt werden. Falls zwischen großen und kleinen Buchstaben unterschieden werden soll, entfernen wir diese Funktion aus dem Programmkode.
= tolower(novels_txt$text) %>% str_extract_all(pattern = "[:alpha:]{1}")
letters 1]][1:10] letters[[
## [1] "d" "e" "r" "p" "r" "o" "z" "e" "s" "s"
2]][1:9] letters[[
## [1] "t" "o" "m" "s" "a" "w" "y" "e" "r"
8.3.2 aus Datensatz
Tabellen und Graphiken erstellen ist leichter, wenn wir die Texte in Datensätze umwandeln, und zwar mit der Funktion as.data.frame().
= as.data.frame(novels_txt) novels
Mit der Funktion unnest_tokens() können wir auch Buchstaben isolieren und anschließend auszählen.
library(tidytext)
<- novels %>%
novels_character unnest_tokens(character, text, token = "characters", to_lower = TRUE, drop = T)
head(novels_character)
## doc_id title character
## 1 prozess.txt prozess d
## 2 prozess.txt prozess e
## 3 prozess.txt prozess r
## 4 prozess.txt prozess p
## 5 prozess.txt prozess r
## 6 prozess.txt prozess o
8.4 Buchstaben zählen
Mit count() können wir die Häufigkeit einer Variable (hier: der Buchstaben) auszählen.
%>%
novels_character count(character, sort = TRUE) %>% head(3)
## character n
## 1 e 114769
## 2 n 70151
## 3 i 52767
Der tidytext-Tokenizer hat nicht nur Buchstaben, sondern auch Zahlen extrahiert. Da wir nur an der Häufigkeit von Buchstaben interessiert sind, filtern wir die Zahlen und andere Zeichen heraus. Dazu verwenden wir die Funktionen filter() und zusätzlich str_detect(), da wir für diese Aufgabe einen regulären Ausdruck nutzen wollen.
%>%
novels_character filter(str_detect(character, "[:alpha:]")) %>%
count(character, sort = T) %>% head(3)
## character n
## 1 e 114769
## 2 n 70151
## 3 i 52767
Ein paar Zeichen, die nicht zum deutschen Alphabet gehören und mit dem vorherigen Programm-Schritt nicht herausfiltern konnten, werden im nächsten Schritt ebenfalls herausgefiltert.
Wir speichern das Ergebnis als neue Tabelle mit dem Namen char_freq. Die zehn häufigsten Buchstaben in dieser Tabelle decken sich mit denen in den beiden eingangs gezeigten Tabellen aus dem Wikipedia-Artikel über Buchstabenhäufigkeit, insbesondere mit der, die auf einem Briefkorpus beruhte.
= novels_character %>%
char_freq filter(str_detect(character, "[:alpha:]")) %>%
filter(!str_detect(character, "é|á")) %>%
count(character, sort = T)
library(DT)
%>%
char_freq ::datatable(fillContainer = FALSE, filter = "top",
DToptions = list(pageLength = 10))
Insgesamt haben wir 30 Buchstaben des deutschen Alphabets in den Romanen unterschieden. Aus wie vielen Buchstaben des deutschen Alphabets bestehen die Romane? Die Summe erhalten wir mit der Funktion summarise() - fast 700 Tausend.
%>%
char_freq summarise(total = sum(n))
## total
## 1 694556
Es ist nun wirklich Zeit, mal ein Bild zu malen! Dazu verwenden wir das Programm (library) ggplot2, das im Programmbündel tidyverse enthalten ist.
Das Diagramm zeigt sehr deutlich, dass gewaltige Häufigkeitsunterschiede im deutschen Alphabet bestehen.
%>%
char_freq mutate(character = fct_reorder(character, n)) %>% # Sortieren nach Frequenz
ggplot(aes(n, character, fill = character)) +
geom_col() +
theme(legend.position = "none")
Eine bessere Vorstellung von den Zahlenverhältnissen erhalten wir, wenn wir die mehrstelligen Zahlenwerte in Prozente umwandeln.
library(scales)
%>%
char_freq mutate(Prozent = n / sum(n)) %>% # Umwandlung in Prozente
ungroup() %>%
mutate(character = fct_reorder(character, Prozent)) %>% # Sortieren nach Prozenten
ggplot(aes(Prozent, character, fill = character)) +
geom_col() +
theme(legend.position = "none") +
scale_x_continuous(labels = percent_format(
decimal.mark = ",", accuracy = 1)) # Prozent-Format
Getrennte tabellarische Darstellung für die Texte:
%>%
novels_character group_by(doc_id) %>%
count(character, sort = TRUE) %>%
pivot_wider(names_from = doc_id, values_from = n) %>%
::datatable(fillContainer = FALSE, filter = "top",
DToptions = list(pageLength = 10))
Getrennte graphische Darstellung für die Texte:
library(scales)
%>%
novels_character group_by(doc_id) %>%
count(character, sort = TRUE) %>%
mutate(Prozent = n / sum(n)) %>% # Umwandlung in Prozente
ungroup() %>%
mutate(character = fct_reorder(character, Prozent)) %>% # Sortieren nach Prozenten
filter(Prozent > 0.0001) %>%
ggplot(aes(Prozent, character, fill = character)) +
geom_col() +
theme(legend.position = "none") +
facet_wrap(~ doc_id, scales = "free")
scale_x_continuous(labels = percent) # Prozent-Format
## <ScaleContinuousPosition>
## Range:
## Limits: 0 -- 1
8.5 Vokale
Betrachten wir zunächst nur die Buchstaben, die Vokale symbolisieren! Zu diesem Zweck bilden wir eine Vokalliste. Zwischen den Vokalen setzen wir das “oder”-Zeichen ein: den logischen Operator “|”.
= "a|e|i|o|u|ä|ö|ü|y" vokale
Die Vokalliste “vokale” verwenden wir mit den Funktionen filter() und str_detect().
library(scales)
%>%
char_freq filter(str_detect(character, vokale)) %>%
mutate(Prozent = n / sum(n)) %>% # Umwandlung in Prozente
ungroup() %>%
mutate(character = fct_reorder(character, Prozent)) %>% # Sortieren nach Prozenten
ggplot(aes(Prozent, character, fill = character)) +
geom_col() +
theme(legend.position = "none") +
labs(y = "Vokale", x = "Häufigkeit in Romanen") +
scale_x_continuous(labels = percent_format(accuracy = 1),
breaks = seq(0, 0.50, 0.05)) # Prozent-Format
Am häufigsten kommt der Buchstabe “e” in den Romanen vor (fast 45%-iger Anteil unter den Vokalen!), am seltensten “y”, welches im Wesentlichen in Fremd- und Lehnwörtern auftritt.
8.6 Konsonanten
Welche Buchstaben, die Konsonanten symbolisieren, kommen am häufigsten vor? Zum Filtern verwenden wir wiederum die Vokalliste, dieses Mal allerdings mit Negationszeichen “!”.
library(scales)
%>%
char_freq filter(!str_detect(character, vokale)) %>% # Negationszeichen, daher Konsonanten beibehalten
mutate(Prozent = n / sum(n)) %>% # Umwandlung in Prozente
ungroup() %>%
mutate(character = fct_reorder(character, Prozent)) %>% # Sortieren nach Prozenten
ggplot(aes(Prozent, character, fill = character)) +
geom_col() +
theme(legend.position = "none") +
labs(y = "Konsonanten", x = "Häufigkeit in Romanen") +
scale_x_continuous(labels = percent_format(accuracy = 1), breaks = seq(0, 0.50, 0.02)) # Prozent-Format und Einheiten
Der Buchstabe “n” kommt in den Romanen am häufigsten vor, gefolgt von den Buchstaben: “r, s, t, h, d”. Selten sind die Buchstaben: “x, q, p, ß, v”.
8.7 Vokal-Konsonant-Verhältnis
Welches Zahlenverhältnis besteht zwischen den Vokalen und Konsonanten?
21 konsonantische Buchstaben und 9 vokalische Buchstaben. Pro Silbe sind in den deutschen Texten 1 Vokal und ungefähr 2 Konsonanten zu erwarten, also Silbenstrukturen wie z.B. KVK, KKV, VKK.
= char_freq %>%
bs_ratio mutate(buchstabe = ifelse(str_detect(character, vokale), "Vokal", "Konsonant")) %>%
count(buchstabe) %>%
mutate(Prozent = n / sum(n))
bs_ratio
## buchstabe n Prozent
## 1 Konsonant 21 0.7
## 2 Vokal 9 0.3
Der höhere Anteil der Konsonanten entspricht der größeren Konsonantenmenge.
ggplot(bs_ratio, aes(x = "", y = Prozent, fill = buchstabe)) +
geom_col(color = "black", size = 2) +
coord_polar(theta = "y", start = -0 * pi / 180) +
# scale_fill_discrete(labels = c("Konsonanten", "Vokale")) +
scale_fill_manual(labels = c("Konsonanten", "Vokale"),
values = c("#9E9AC8", "#6A51A3")) +
theme(legend.position = "top", axis.text.y = element_blank(),
axis.ticks = element_blank()) +
labs(y = "", x = "Anteil % in Romanen") +
scale_x_discrete(NULL, expand = c(0, 0)) +
scale_y_continuous(
labels = percent_format(accuracy = 1),
breaks = seq(0, 1, 0.1)) # Prozent-Format und Einheiten
Diese Zahlenwerte und -verhältnisse bilden einen möglichen Ausgangspunkt für intra- oder interlinguale Vergleiche.
8.8 Anzahl der Silben
Wie viele Silben kommen in den Romanen schätzungsweise vor und wie viele Buchstaben pro Silbe? Die genaue Bestimmung der Silbenanzahl für eine bestimmte Sprache kann aufgrund zahlreicher Besonderheiten ziemlich kompliziert sein. Die Anzahl der Silben schätzen wir daher mit einer Funktion des Programms nsyllable (Alternatives Programm: qdap).
Da wir die Silbenzählfunktion nur ein einziges Mal bemühen, rufen wir sie in der unten sichtbaren Form auf: nsyllable::nsyllable(buchstabenfolge).
= novels %>%
novels_words unnest_tokens(word, text, token = "words", to_lower = TRUE, drop = T) %>%
mutate(syllables = nsyllable::nsyllable(as.character(word), language = "en")) %>%
mutate(letters = nchar(word))
%>% head(100) %>%
novels_words ::datatable(fillContainer = FALSE, filter = "top",
DToptions = list(pageLength = 6))
Insgesamt (d.h. kumulativ gesehen) fast 139 Tausend Silben in den Romanen. Diese Zahl bietet einen möglichen Ausgangspunkt für Textvergleiche.
%>%
novels_words count(syllables) %>%
summarise(Silben = sum(n))
## Silben
## 1 138811
Die meisten Wortformen in den Romanen bestehen aus einer Silbe (fast 60%) oder zwei Silben (fast 30%). Das ist typisch für deutsche Texte. Kurze Funktionswörter (meist eine Silbe) kommen wesentlich häufiger vor als andere Wortklassen (Substantive, Verben, Adjektive, Adverbien).
%>%
novels_words count(syllables) %>%
mutate(Prozent = n / sum(n)) %>%
ggplot(aes(syllables, Prozent, fill = factor(syllables))) +
geom_col() +
theme(legend.position = "none") +
labs(x = "Silben") +
scale_y_continuous(
labels = percent,
breaks = seq(0, 0.75, 0.1)) # Prozent-Format und Einheiten
## Warning: Removed 1 rows containing missing values (position_stack).
Berücksichtig man lediglich distinktive Wortformen (also keine Wortwiederholungen), dann ergibt sich die folgende Verteilung, in der die Zweisilber (mehr als 30%) und Dreisilber (fast 30%) den größten Anteil haben.
%>%
novels_words distinct(word, .keep_all = T) %>%
count(syllables) %>%
mutate(Prozent = n / sum(n)) %>%
ggplot(aes(syllables, Prozent, fill = factor(syllables))) +
geom_col() +
theme(legend.position = "none") +
labs(x = "Silben") +
scale_y_continuous(
labels = percent,
breaks = seq(0, 0.75, 0.1)) # Prozent-Format und Einheiten
## Warning: Removed 1 rows containing missing values (position_stack).
8.9 Mittlere Wortlänge
Wir können die Wortlänge in geschriebenen Texten auf zumindest zwei grundlegende Arten messen: - die Anzahl der Silben pro Wort(form), - die Anzahl der Buchstaben pro Wort(form).
Die durchschnittliche Anzahl der Silben und Buchstaben pro Wort (distinkte Wortformen !) in den Romanen ist in der folgenden Tabelle zu sehen: - neben den Mittelwerten (Avg_Silben, Avg_Buchstaben) - auch die Standardabweichungen vom entsprechenden Mittelwert (Stdev_Silben, Stdev_Buchstaben). Die Mittelwerte oder arithmetischen Mittel können mit der Programmfunktion mean() berechnet werden, die Standardabweichungen mit der Funktion sd(). Die Standardabweichungen sind notwendig zur Feststellung nicht-zufälliger Unterschiede zwischen den Stichproben (d.h. den Romanen). Bei der Berechnung der Mittelwerte und Standardabweichungen geben wir dem Programm auch die Instruktion, etwaige leere Datenzeilen (NA) herauszufiltern, und zwar mit Hilfe von na.rm = TRUE. Wird dies unterlassen, kann dies dazu führen, dass ein Mittelwert bzw. Standardabweichung nicht berechnet werden kann.
In der folgenden Tabelle werden nur distinktive Wortformen (Types) berücksichtigt, d.h. als ob jede Wortform nur einmal pro Roman vorkommt.
%>%
novels_words group_by(title) %>%
distinct(word, .keep_all = T) %>%
add_count(word) %>%
summarise(Avg_Silben = mean(syllables, na.rm = TRUE),
Stdev_Silben = sd(syllables, na.rm = TRUE),
Avg_Buchstaben = mean(letters, na.rm = TRUE),
Stdev_Buchstaben = sd(letters, na.rm = TRUE)) %>%
::datatable(fillContainer = TRUE, filter = "top",
DToptions = list(pageLength = 4))
Die durchschnittliche Anzahl der Silben und Buchstaben pro Wortform (Token), bei Berücksichtigung von Wortwiederholungen in den Romanen, ist in der folgenden Tabelle zu sehen.
%>%
novels_words group_by(title) %>%
summarise(Avg_Silben = mean(syllables, na.rm = TRUE),
Stdev_Silben = sd(syllables, na.rm = TRUE),
Avg_Buchstaben = mean(letters, na.rm = TRUE),
Stdev_Buchstaben = sd(letters, na.rm = TRUE)) %>%
::datatable(fillContainer = TRUE, filter = "top",
DToptions = list(pageLength = 4))
8.10 Testen von Mittelwertunterschieden
8.10.1 t-Test
Sind die berechneten Unterschiede zwischen den Mittelwerten relevant bzw. nicht-zufällig? Um diese Frage zu klären, kann man einen statistischen Test bemühen. Da wir lediglich zwei Samples (zwei Romanen) vergleichen wollen, kann uns ein parametrischer Test wie z.B. der t-Test Klarheit verschaffen. Wir verwenden die Programmfunktion t.test(). Der t-Test bestätigt, dass “Der Prozess” im Durchschnitt etwas längere Wörter aufweist (2,59 Silben pro Wort gegenüber 2,44 Silben pro Wort in “Tom Sawyer”) - wenn Anzahl distinktiver Wortformen verwendet.
= novels_words %>%
syls group_by(title) %>%
distinct(word, .keep_all = T) %>%
add_count(word) %>%
drop_na() %>%
::select(title, word, syllables) %>%
dplyrpivot_wider(names_from = title, values_from = syllables)
t.test(syls$prozess, syls$tom, var.equal = TRUE) # significant
##
## Two Sample t-test
##
## data: syls$prozess and syls$tom
## t = 9.5813, df = 17554, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.1263172 0.1912927
## sample estimates:
## mean of x mean of y
## 2.598953 2.440148
Wenn die Wiederholung von Wortformen berücksichtigt wird, bestätigt der t-Test ebenfalls einen signifikanten Unterschied zwischen den beiden Texten. Die durchschnittliche Anzahl der Wortsilben ist niedriger, da kürzere Wortformen (solche von Konjunktionen, Präpositionen, Artikeln und anderen Funktionswörtern) häufig vorkommen.
Schnelle Form des t-Tests:
t.test(syllables ~ title, data = novels_words, var.equal = TRUE)
##
## Two Sample t-test
##
## data: syllables by title
## t = 11.37, df = 137403, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group prozess and group tom is not equal to 0
## 95 percent confidence interval:
## 0.04307118 0.06101337
## sample estimates:
## mean in group prozess mean in group tom
## 1.613350 1.561307
Dasselbe Ergebnis, aber aufwendiger zu programmieren, um den Datensatz in die entsprechende Form zu bringen:
<- novels_words %>%
prozess_syl filter(title == "prozess") %>%
::select(syllables) %>%
dplyrrename(prozess = syllables)
<- novels_words %>%
tom_syl filter(title == "tom") %>%
::select(syllables) %>%
dplyrrename(prozess = syllables)
t.test(prozess_syl, tom_syl, var.equal = T) # significant
##
## Two Sample t-test
##
## data: prozess_syl and tom_syl
## t = 11.37, df = 137403, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.04307118 0.06101337
## sample estimates:
## mean of x mean of y
## 1.613350 1.561307
Der nächste t-Test bestätigt ebenfalls, dass “Der Prozess” im Durchschnitt längere Wörter aufweist (8,79 Buchstaben pro Wort gegenüber 8.36 Buchstaben pro Wort in “Tom Sawyer”.) Berücksichtigt wurden distinkte Wortformen (keine wiederholten Wortformen).
= novels_words %>%
lets group_by(title) %>%
distinct(word, .keep_all = T) %>%
add_count(word) %>%
drop_na() %>%
::select(title, word, letters) %>%
dplyrpivot_wider(names_from = title, values_from = letters)
t.test(lets$prozess, lets$tom, var.equal = T) # significant
##
## Two Sample t-test
##
## data: lets$prozess and lets$tom
## t = 9.0317, df = 17554, p-value < 2.2e-16
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.3310081 0.5145045
## sample estimates:
## mean of x mean of y
## 8.785878 8.363122
Wenn die Wiederholung von Wortformen berücksichtigt wird, bestätigt der t-Test wiederum einen signifikanten Unterschied zwischen den beiden Texten. Die durchschnittliche Anzahl der Buchstaben pro Wort ist niedriger, da kürzere Wortformen (solche von Konjunktionen, Präpositionen, Artikeln und anderen Funktionswörtern) häufig vorkommen.
Schnelle Form des t-Tests:
t.test(letters ~ title, data = novels_words, var.equal = TRUE)
##
## Two Sample t-test
##
## data: letters by title
## t = 5.0147, df = 138809, p-value = 5.319e-07
## alternative hypothesis: true difference in means between group prozess and group tom is not equal to 0
## 95 percent confidence interval:
## 0.04480651 0.10230526
## sample estimates:
## mean in group prozess mean in group tom
## 5.048860 4.975304
Dasselbe Ergebnis, aber aufwendiger zu programmieren, um den Datensatz in die entsprechende Form zu bringen:
<- novels_words %>%
prozess_let filter(title == "prozess") %>%
::select(letters) %>%
dplyrrename(prozess = letters)
<- novels_words %>%
tom_let filter(title == "tom") %>%
::select(letters) %>%
dplyrrename(prozess = letters)
t.test(prozess_let, tom_let, var.equal = TRUE) # significant
##
## Two Sample t-test
##
## data: prozess_let and tom_let
## t = 5.0147, df = 138809, p-value = 5.319e-07
## alternative hypothesis: true difference in means is not equal to 0
## 95 percent confidence interval:
## 0.04480651 0.10230526
## sample estimates:
## mean of x mean of y
## 5.048860 4.975304
8.10.2 Lineare Regression
Hat man mehr als zwei Stichproben zu vergleichen, kann man eine lineare Regression durchführen, die auch das Testen von mehreren Einflussgrößen (Prädiktoren) erlaubt.
Hier folgt eine Demonstration anhand des bereits gehabten Datensatzes mit zwei Stichproben (Romanen). Die Ordinate im Koordinatensystem (Intercept, also der y-Abschnitt mit x = 0) ist bei zwei Stichproben gleich dem Mittelwert der ersten Stichprobe (title = “prozess”), d.h. 1,613350. Der geschätzte Mittelwert (Estimate) der zweiten Stichprobe (title = “tom”) ist um den Wert 0,052042 niedriger, d.h. 1,613350 - 0,052042 = 1,561308 (Dezimalkommas statt Dezimalpunkte!).
Der R-Quadrat-Wert (R-squared) ist allerdings sehr klein, d.h. dass der Prädiktor “title” (Roman) nur einen Bruchteil der festgestellten Mittelwertvarianz (Veränderungen der Mittelwerte) zu erklären vermag. Möglicherweise gibt es andere Prädiktoren, die die Mittelwertvarianz besser erklären.
<- lm(syllables ~ title, data = novels_words)
m summary(m)
##
## Call:
## lm(formula = syllables ~ title, data = novels_words)
##
## Residuals:
## Min 1Q Median 3Q Max
## -0.6133 -0.6133 -0.5613 0.4387 6.3867
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.613350 0.003183 506.85 <2e-16 ***
## titletom -0.052042 0.004577 -11.37 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 0.8479 on 137403 degrees of freedom
## (1406 observations deleted due to missingness)
## Multiple R-squared: 0.00094, Adjusted R-squared: 0.0009327
## F-statistic: 129.3 on 1 and 137403 DF, p-value: < 2.2e-16
anova(m)
## Analysis of Variance Table
##
## Response: syllables
## Df Sum Sq Mean Sq F value Pr(>F)
## title 1 93 92.937 129.28 < 2.2e-16 ***
## Residuals 137403 98778 0.719
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Graphische Darstellung: der Mittelwertunterschied ist gering (nur 0,05 Silben), aber aufgrund der großen Stichproben statistisch signifikant. Der Faktor “title” erklärt nur einen verschwinded kleinen Bruchteil der Mittelwertunterschiede.
library(effects)
## Loading required package: carData
## lattice theme set by effectsTheme()
## See ?effectsTheme for details.
allEffects(m)
## model: syllables ~ title
##
## title effect
## title
## prozess tom
## 1.613350 1.561307
plot(allEffects(m))
Ergebnisse in Tabellenform:
summary(lm(syllables ~ title, data = novels_words)) %>%
::tidy() %>%
broom::datatable(fillContainer = TRUE, filter = "top",
DToptions = list(pageLength = 4))
Boxplot mit Jitterplot anhand des vollen Datensatzes: der Mittelwert ist hier der Median median() (d.h. ein Wert, der genau in der Mitte jeder Stichprobe liegt), das arithmetische Mittel / der Durchschnitt wird hier mit einem roten Quadrat symbolisiert. Der Median liegt in beiden Stichproben beim Wert 1, also weit unter dem jeweiligen Durchschnittswert. Dies zeigt, dass die Wortlängen nicht normalverteilt sind. Der Jitterplot veranschaulicht, dass der “Prozess” über mehr Wortformen mit 6, 7 oder 8 Silben verfügt.
%>%
novels_words group_by(title) %>%
ggplot(aes(title, syllables, fill = title, group = title)) +
geom_jitter(width = 0.4, alpha = 0.5, color = "gray70") +
geom_boxplot(notch = FALSE, width = 0.8) +
stat_summary(fun.y="mean", color = "red", shape = 15)+
expand_limits(y = -1) +
scale_y_continuous(breaks = seq(-1, 8, 1)) +
theme(legend.position = "none") +
labs(y = "Mittlere Wortlänge (in Silben)", x = "Roman")
## Warning: `fun.y` is deprecated. Use `fun` instead.
## Warning: Removed 1406 rows containing non-finite values (stat_boxplot).
## Warning: Removed 1406 rows containing non-finite values (stat_summary).
## Warning: Removed 1406 rows containing missing values (geom_point).
## Warning: Removed 2 rows containing missing values (geom_segment).
Boxplot anhand der zusammengefassten Daten (Durchschnitt, Standardabweichung):
= novels_words %>%
df group_by(title) %>%
summarise(Avg_Silben = mean(syllables, na.rm = TRUE),
Stdev_Silben = sd(syllables, na.rm = TRUE),
Avg_Buchstaben = mean(letters, na.rm = TRUE),
Stdev_Buchstaben = sd(letters, na.rm = TRUE))
%>% ggplot(aes(title, Avg_Silben, fill = title, group = title)) +
df geom_pointrange(aes(ymin = Avg_Silben - Stdev_Silben, ymax = Avg_Silben + Stdev_Silben),
stat="identity") +
theme(legend.position = "none") +
labs(y = "Mittlere Wortlänge (in Silben)")
%>% ggplot(aes(title, fill = title, group = title)) +
df geom_boxplot(aes(lower = Avg_Silben - Stdev_Silben, upper = Avg_Silben + Stdev_Silben,
middle = Avg_Silben,
ymin = Avg_Silben - 3*Stdev_Silben, ymax = Avg_Silben + 3*Stdev_Silben),
stat="identity") +
theme(legend.position = "none") +
labs(y = "Mittlere Wortlänge (in Silben)")
8.11 Quanteda-Funktionen
Eine alternative Berechnung der Anzahl der Buchstaben pro Wort mit quanteda (ohne t-Test).
Die Durchschnittswerte, die uns quanteda liefert, sind etwas höher als die tidyverse-Werte. Aber auch hier ist der Mittelwert für den “Prozess” höher als für “Tom Sawyer”.
library(quanteda)
library(quanteda.textstats)
= corpus(novels_txt)
corp = textstat_summary(corp)
stats
%>% paged_table() stats
%>%
stats group_by(document) %>%
transmute(buchstaben = (chars-puncts)/tokens) %>% paged_table()
Die Durchschnittswerte unterscheiden sich in den Berechnungen (tidyverse vs. quanteda), was mit der verschiedenen Art der Tokenisierung und der Aussonderung von nicht relevanten Tokens und leeren Datenzeilen (NA) zu tun hat.
8.12 Konsonantenverbindungen
Welche Konsonantenverbindungen (Buchstabenverbindungen) kommen häufiger vor in den Texten? Wir zerlegen die Texte im Korpus in kleinere Einheiten (mittels tokens()), aber dieses Mal in alphanumerische Zeichen (Buchstaben). Anschließend wenden wir char_ngrams()-Funktion an, mit der man Verknüpfungen von Zeichen feststellen kann.
= tokens(corp, what = "character", remove_punct = TRUE, remove_symbols = T, remove_numbers = T, remove_url = T, remove_separators = T)
tok_ch
= char_ngrams(as.character(tok_ch), n = c(2,3,4), concatenator = "") ngrams_ch
Wir wandeln die ngram-Liste in einen Datensatz um (mittels tibble()), was das Zählen mit einer tidyverse-Funktion ermöglicht.
= ngrams_ch %>%
ngrams_char as_tibble() %>%
rename(cluster = value)
%>%
ngrams_char count(cluster, sort = TRUE) %>%
head(10) %>%
::datatable(fillContainer = FALSE, filter = "top",
DToptions = list(pageLength = 10))
to be continued …
8.13 Datensatz-Variante
<- novels %>%
novels_words_char unnest_tokens(word, text, token = "words", to_lower = TRUE, drop = T) %>%
mutate(Silben = nsyllable::nsyllable(as.character(word), language = "en")) %>%
unnest_tokens(character, word, token = "characters", to_lower = TRUE, drop = F) %>%
mutate(buchstabe = ifelse(str_detect(character, vokale), "Vokal", "Konsonant"))
head(novels_words_char) %>%
head(10) %>%
::paged_table() rmarkdown