Lerne, wie du eine Tidy-Sentimentanalyse in R an Princes Songs durchführst: Sentiment über die Zeit, Song-Ebene, der Einfluss von Bigrammen und vieles mehr!
Begib dich auf eine emotionale Reise durch Leben und Werk von Prince, The Artist — Teil Zwei-A einer dreiteiligen Tutorialserie, in der wir mithilfe von Sentimentanalyse mit R Einblicke in die Karriere und den gesellschaftlichen Einfluss des Künstlers gewinnen. Die drei Tutorials behandeln:
Wusstest du, dass Prince angeblich schon drei Jahre vor 9/11 auf der Bühne darauf anspielte? Vielleicht sagte er auch seinen eigenen Tod voraus. Stimmt das? Wäre es nicht spannend, seine Songtexte zu untersuchen und dir selbst ein fundiertes Urteil zu bilden? Am 21. April 1985 nahm er "Sometimes It Snows In April" auf. Genau 31 Jahre später, am 21. April 2016, starb er. Schau dir seine Wortwahl in der Sentimentanalyse dieses Songs an:
Sentimentanalyse von "Sometimes It Snows In April"
Das gibt Predictive Analytics eine ganz neue Bedeutung!
Aufbau
Auf jeden Codeabschnitt folgt eine — meist subjektive — Interpretation. Du kannst gerne zwischen den Abschnitten springen und eigene Annahmen treffen! Ich empfehle die folgende Einführung zu lesen. Wenn du direkt in den Code einsteigen möchtest, klicke hier. Wenn dir Lexika bereits vertraut sind, nimm die Abkürzung direkt zum Abschnitt mit der detaillierten Analyse.
Einführung
Triplets: Drei-Teile-Zusammenfassung
Ich habe gerade zwei Kätzchen und einen Welpen gerettet, daher denke ich jetzt in Dreiergruppen. Außerdem besagt die Regel der Drei, dass Dinge in Dreierpackung oft witziger, befriedigender oder wirkungsvoller sind — deshalb habe ich das Tutorial in drei Teile aufgeteilt. In Teil Eins hast du Text Mining und explorative Analysen anhand eines Datensatzes mit Hunderten Songtexten des legendären Prince kennengelernt. In Teil Zwei fokussierst du Sentimentanalyse (Zwei-A) und Topic Modeling (Zwei-B) mit NLP. Teil Drei schließt mit weiteren Predictive-Analytics-Aufgaben via Machine Learning: etwa, ob sich das Veröffentlichungsjahrzehnt eines Songs bestimmen lässt oder — spannender — ob sich anhand der Lyrics der Einzug in die Billboard-Charts vorhersagen lässt. In allen Tutorials nutzt du unterschiedliche Machine-Learning-Algorithmen — im folgenden Schaubild jeweils rot markiert.
Adaptiert nach der Grafik hier. Beachte: Aspekte des Modellierens und der ML-Verfahren lassen sich nicht immer eindeutig einer Box zuordnen. Die Grafik dient der Orientierung.
Trennung fällt schwer
Ja, ich habe drei Teile versprochen. Aber Teil Zwei habe ich in Zwei-A (Sentimentanalyse) und Zwei-B (Topic Modeling – folgt in Kürze) gesplittet, also sind es am Ende vier Tutorials. Es gibt viele starke Artikel und Kurse zur Methodik der Sentimentanalyse. Ziel von Teil Zwei-A ist daher, den Fokus auf Erkenntnisse aus der Analyse zu legen — nicht nur auf den Code. Wir nutzen Prince’ Lyrics als Beispiel, aber die Schritte lassen sich auf jede:n Lieblingskünstler:in übertragen. Die Serie soll Neugier auf die Möglichkeiten der Lyrikanalyse wecken — und nebenbei in Natural Language Processing (NLP) und Machine Learning einführen.
Voraussetzungen
Für Teil Zwei-A brauchst du Grundkenntnisse zu tidy Data — insbesondere Pakete wie dplyr für Transformationen, ggplot2 für Visualisierungen und den %>%-Pipe-Operator (ursprünglich aus magrittr). Du wirst sehen, dass mehrere Schritte oft per %>% kompakt und wiederverwendbar verknüpft werden.
Da dieses Tutorial auch eine Fallstudie ist, ist es reich an Visualisierungen über die üblichen Diagramme hinaus. Du solltest daher in deiner Umgebung neue Pakete installieren können. Um den Fokus auf Erkenntnisse zu legen, habe ich Code beigefügt, den du auf eigene Datensätze anwenden kannst; die Detailerklärung erfordert stellenweise eigene Recherche. Zu jedem Paket gibt es unterstützende Links. So lernst du neue Tools kennen und konzentrierst dich gleichzeitig auf deren Anwendung.
Tipp: Lies zuerst Teil Eins, um eine Grundlage für die Serie und einen Einblick in das dichte und vielfältige Vokabular von Prince’ Lyrics zu bekommen.
Überblick Sentimentanalyse
Methoden: Sentimentanalyse ist eine Form des Text Minings, die Meinung und Subjektivität eines Textes bestimmen will. Bei Lyrics spiegeln die Ergebnisse nicht nur die Haltung der Künstler:innen, sondern auch kulturelle Strömungen. Es gibt verschiedene Ansätze: Modelle auf Basis trainierter Datensätze, regelbasierte Klassifikatoren und vordefinierte lexikalische Wörterbücher (Lexika). In diesem Tutorial verwenden wir den lexikonbasierten Ansatz — ich empfehle dir aber, auch die anderen Methoden und ihre Trade-offs zu erkunden.
Ebenen: Neben verschiedenen Methoden gibt es Analyse-Ebenen abhängig vom Text: Dokument, Satz und Wort. Bei Lyrics kann ein Dokument etwa das Sentiment pro Jahrzehnt, Jahr, Chart-Level oder Song sein. Die Satzebene ist bei Lyrics selten sinnvoll, da Interpunktion zugunsten von Reim und Rhythmus weggelassen wird. Die Wortebene liefert Details und dient als Grundlage für fortgeschrittene Methoden wie Topic Modeling.
Formuliere deine Fragen!
Jedes Data-Science-Projekt braucht Leitfragen. Behalte beim Durcharbeiten im Hinterkopf: Lässt sich ein Programm schreiben, das die Stimmung eines Liedtexts erkennt? Reichen vordefinierte Lexika aus? Wie viel Datenvorbereitung ist nötig? Lassen sich Ergebnisse mit realen Ereignissen verknüpfen? Ändert sich Sentiment über die Zeit? Sind Hits positiver oder negativer als Nicht-Chart-Songs? Welche Wörter stechen in dem Jahr hervor, in dem Prince 9/11 vorhergesagt haben soll? Hat er seinen eigenen Tod vorhergesagt?
Tipp: Lyrikanalyse ist anders — und oft komplexer — als Reden oder Bücher. Einsichten sind schwerer zu greifen. Sei also vorsichtig mit Annahmen! Ich stelle in diesem Tutorial mehr Fragen als Antworten — denk außerhalb des Quadrats!
Vorbereitung
Libraries und Funktionen
Um mit der Analyse von Prince’ Lyrics zu starten, lade die folgenden Libraries. Das wirkt viel, aber die meisten sind für Grafiken. Da wir viele Visuals nutzen, ist eine einheitliche Farbpalette hilfreich. Ich habe eine Liste mit speziellen ANSI-Farbcodes erstellt. Die theme()-Funktion aus ggplot2 erlaubt individuelles Styling, daher erstellen wir eine eigene Funktion theme_lyrics(), die Default-Einstellungen anpasst. knitr ist ein Motor für dynamische Berichte mit R. Zusammen mit kableExtra und formattable erzeugst du ansprechende farbige Tabellen. Erstelle zudem my_kable_styling(), um die Ausgabe dieser Libraries zu standardisieren. Weitere Pakete erkläre ich, wenn wir sie einsetzen.
library(dplyr)#Data manipulation (also included in the tidyverse package)
library(tidytext)#Text mining
library(tidyr)#Spread, separate, unite, text mining (also included in the tidyverse package)
library(widyr)#Use for pairwise correlation#Visualizations!
library(ggplot2)#Visualizations (also included in the tidyverse package)
library(ggrepel)#`geom_label_repel`
library(gridExtra)#`grid.arrange()` for multi-graphs
library(knitr)#Create nicely formatted output tables
library(kableExtra)#Create nicely formatted output tables
library(formattable)#For the color_tile function
library(circlize)#Visualizations - chord diagram
library(memery)#Memes - images with plots
library(magick)#Memes - images with plots (image_read)
library(yarrr)#Pirate plot
library(radarchart)#Visualizations
library(igraph)#ngram network diagrams
library(ggraph)#ngram network diagrams#Define some colors to use throughout
my_colors <- c("#E69F00","#56B4E9","#009E73","#CC79A7","#D55E00","#D65E00")#Customize ggplot2's default theme settings#This tutorial doesn't actually pass any parameters, but you may use it again in future tutorials so it's nice to have the options
theme_lyrics <-function(aticks = element_blank(),
pgminor = element_blank(),
lt = element_blank(),
lp ="none"){
theme(plot.title = element_text(hjust =0.5),#Center the title
axis.ticks = aticks,#Set axis ticks to on or off
panel.grid.minor = pgminor,#Turn the minor grid lines on or off
legend.title = lt,#Turn the legend title on or off
legend.position = lp)#Turn the legend on or off}#Customize the text tables for consistency using HTML formatting
my_kable_styling <-function(dat, caption){
kable(dat,"html", escape =FALSE, caption = caption)%>%
kable_styling(bootstrap_options = c("striped","condensed","bordered"),
full_width =FALSE)}Bereitgestellt von
Sushi-Daten
Wie immer startest du damit, die Rohdaten in ein Data Frame einzulesen. In Teil Eins hast du den ursprünglichen Datensatz aufbereitet: Kontraktionen erweitert, Escape-Sequenzen entfernt, Text kleingeschrieben. Den bereinigten Datensatz hast du als CSV gespeichert. Nutze read.csv(), um prince_data zu erstellen — die Lyrics sollen Zeichenketten bleiben, also setze stringsAsFactors = FALSE. Da du keine Zeilennummern brauchst, setze row.names = 1.
Hinweis: Du könntest diese Parameter vermeiden, indem du als modernes Data Frame ein tibble via read_csv() einliest. Aus Konsistenzgründen mit Teil Eins verwenden wir hier aber read.csv().
prince_data <- read.csv('prince_new.csv', stringsAsFactors =FALSE, row.names =1)Bereitgestellt von
Verschaff dir kurz einen Überblick über die Daten:
Wörter mit weniger als drei Zeichen entfernen (in Musik oft nur phonetischer Effekt)
Lyrics in einzelne Wörter aufteilen
Um Rohdaten in ein tidy Format zu bringen, nutzt du unnest_tokens() aus tidytext und erstellst prince_tidy — ein Wort pro Zeile inklusive zugehörigem Song. Dann helfen anti_join() und filter() aus dplyr beim weiteren Säubern. (Details findest du in Teil Eins.)
#Created in the first tutorial
undesirable_words <- c("prince","chorus","repeat","lyrics","theres","bridge","fe0f","yeah","baby","alright","wanna","gonna","chorus","verse","whoa","gotta","make","miscellaneous","2","4","ooh","uurh","pheromone","poompoom","3121","matic"," ai "," ca "," la ","hey"," na "," da "," uh "," tin "," ll","transcription","repeats","la","da","uh","ah")#Create tidy text format: Unnested, Unsummarized, -Undesirables, Stop and Short words
prince_tidy <- prince_data %>%
unnest_tokens(word, lyrics)%>%#Break the lyrics into individual words
filter(!word %in% undesirable_words)%>%#Remove undesirables
filter(!nchar(word)<3)%>%#Words like "ah" or "oo" used in music
anti_join(stop_words)#Data provided by the tidytext packageBereitgestellt von
glimpse(prince_tidy)#From `dplyr`, better than `str()`.Bereitgestellt von
prince_tidy liegt nun tokenisiert vor: ein Wort pro Zeile inklusive zugehörigem Song. Das Data Frame umfasst 76.116 Wörter und 10 Spalten.
Deskriptive Statistiken
Falls du Teil Eins nicht gelesen hast, lohnt ein kurzer Blick auf ein paar Übersichtsplots des gesamten Datensatzes. Wir nutzen kreative Grafiken aus ggplot2, circlize und yarrr.
Alles klar Schiff: Wortanzahl pro Song
Ein Pirat sagt shipshape, wenn alles ordentlich und sauber ist. Hier siehst du die aufgeräumten Daten aus einer spannenden Perspektive: die lexikalische Diversität, sprich der Wortschatz der Lyrics über die Zeit. Ein Pirate Plot ist eine fortgeschrittene Darstellung einer stetigen abhängigen Variable (z. B. Wortanzahl) als Funktion einer kategorialen unabhängigen Variable (z. B. Jahrzehnt). Er kombiniert Rohdatenpunkte, deskriptive und inferenzstatistische Informationen in einem Plot. Details zu pirateplot() aus yarrr findest du in diesem Blog.
Erstelle das Data Frame word_summary, das die Anzahl unterschiedlicher Wörter pro Song berechnet. Je vielfältiger die Lyrics, desto größer der Wortschatz. So denkst du dich in die Wortebene ein. Setze für Songs ohne Veröffentlichungsjahr das Feld decade auf "NONE" und vergib mit select() klarere Labels.
word_summary <- prince_tidy %>%
mutate(decade = ifelse(is.na(decade),"NONE", decade))%>%
group_by(decade, song)%>%
mutate(word_count = n_distinct(word))%>%
select(song, Released = decade, Charted = charted, word_count)%>%
distinct()%>%#To obtain one record per song
ungroup()
pirateplot(formula = word_count ~ Released + Charted,#Formula
data = word_summary,#Data frame
xlab =NULL, ylab ="Song Distinct Word Count",#Axis labels
main ="Lexical Diversity Per Decade",#Plot title
pal ="google",#Color scheme
point.o =.2,#Points
avg.line.o =1,#Turn on the Average/Mean line
theme =0,#Theme
point.pch =16,#Point `pch` type
point.cex =1.5,#Point size
jitter.val =.1,#Turn on jitter to see the songs better
cex.lab =.9, cex.names =.7)#Axis label sizeBereitgestellt von
Jeder farbige Punkt steht für einen Song. Der dichte rote Bereich mit dem Wert "NONE" zeigt: Viele Songs im Datensatz haben kein Veröffentlichungsdatum. In den frühen Jahrzehnten ist ein leichter Aufwärtstrend bei der Anzahl einzigartiger Wörter pro Song zu erkennen. Die durchgezogene Linie markiert den Mittelwert pro Jahrzehnt. Das ist wichtig, wenn du das Sentiment über die Zeit analysierst. Princes Wortwahl wird im Verlauf seiner Karriere aussagekräftiger.
Ich empfehle dir, Pirate Plots weiter zu erkunden — wir kratzen hier nur an der Oberfläche!
Rund ums Jahr: Songs pro Jahr
Runde Diagramme eignen sich, um komplexe (oder simple!) Zusammenhänge über viele Kategorien zu zeigen. (Und: Alben waren mal rund!). Der folgende Plot ist ein kreisförmiges Balkendiagramm mit coord_polar() aus ggplot2 und zeigt die relative Anzahl der Songs pro Jahr. Der Code ist umfangreich für eine einfache Grafik — aber es lohnt sich. Ein ähnliches Beispiel findest du detailliert in der R Graph Gallery hier. Wie bei Pirate Plots ist dies nur ein Einstieg in zirkuläre Plots.
Songanzahl pro Jahr
songs_year <- prince_data %>%
select(song, year)%>%
group_by(year)%>%
summarise(song_count = n())
id <- seq_len(nrow(songs_year))
songs_year <- cbind(songs_year, id)
label_data = songs_year
number_of_bar = nrow(label_data)#Calculate the ANGLE of the labels
angle =90-360*(label_data$id -0.5)/ number_of_bar #Center things
label_data$hjust <- ifelse(angle <-90,1,0)#Align label
label_data$angle <- ifelse(angle <-90, angle +180, angle)#Flip angle
ggplot(songs_year, aes(x = as.factor(id), y = song_count))+
geom_bar(stat ="identity", fill = alpha("purple",0.7))+
geom_text(data = label_data, aes(x = id, y = song_count +10, label = year, hjust = hjust), color ="black", alpha =0.6, size =3, angle = label_data$angle, inherit.aes =FALSE)+
coord_polar(start =0)+
ylim(-20,150)+#Size of the circle
theme_minimal()+
theme(axis.text = element_blank(),
axis.title = element_blank(),
panel.grid = element_blank(),
plot.margin = unit(rep(-4,4),"in"),
plot.title = element_text(margin = margin(t =10, b =-10)))Bereitgestellt von
Siehst du die Lücke ganz oben? Das deutet — wie beim Pirate Plot — auf die vielen Songs ohne Veröffentlichungsdatum hin. Bei so vielen unveröffentlichten Songs wäre der Plot mit ihnen kaum lesbar. Fehlende Jahre markieren Jahre ohne Releases. Am produktivsten waren 1996 und 1998.
Warum? Lies weiter!
Akkorde: Chart-Erfolge nach Jahrzehnt
Die nächste Grafik zeigt den Zusammenhang zwischen Veröffentlichungsjahrzehnt und Chart-Erfolg in den Billboard-Charts. Ein chordDiagram() für Musik — passt doch! Das Tool stammt aus dem eleganten circlize-Paket von Zuguang Gu. Der Ring ist in zwei Bereiche geteilt: Chart-Status (oben) und Jahrzehnt (unten). Breite Lücken trennen die Kategorien, kleinere die Ausprägungen. Ausführlichere Erläuterungen zum Code findest du hier.
decade_chart <- prince_data %>%
filter(decade !="NA")%>%#Remove songs without release dates
count(decade, charted)#Get SONG count per chart level per decade. Order determines top or bottom.
circos.clear()#Very important - Reset the circular layout parameters!
grid.col = c("1970s"= my_colors[1],"1980s"= my_colors[2],"1990s"= my_colors[3],"2000s"= my_colors[4],"2010s"= my_colors[5],"Charted"="grey","Uncharted"="grey")#assign chord colors# Set the global parameters for the circular layout. Specifically the gap size
circos.par(gap.after = c(rep(5, length(unique(decade_chart[[1]]))-1),15,
rep(5, length(unique(decade_chart[[2]]))-1),15))
chordDiagram(decade_chart, grid.col = grid.col, transparency =.2)
title("Relationship Between Chart and Decade")Bereitgestellt von
Auf den ersten Blick komplex, zeigt der Kreis dennoch klar die Anzahl der Songs je Jahrzehnt und Chart-Level. Prince startete in den 1970ern mit wenigen Releases, von denen einige charteten. Vergleiche die 1980er mit den 1990ern: In den 1990ern gab es mehr Releases, in den 1980ern aber mehr Chart-Erfolge. In den 2000ern waren nur wenige Songs kommerziell erfolgreich, in den 2010ern gab es keine Hits.
Lexika und Lyrics
In diesem Abschnitt wirst du:
Sentiment-Lexika und ihre Wortumfänge untersuchen
Ermitteln, wie viele Wörter der Lyrics in den Lexika vorkommen
Konkrete Wörter und Wortformen betrachten
Weitere Datenaufbereitung in Erwägung ziehen
Sentiment-Lexika erkunden
Das Paket tidytext enthält den Datensatz sentiments mit mehreren Lexika — das sind Wörterbücher, die Wörtern Kategorien oder Werte zuordnen. tidytext liefert drei allgemeine Lexika:
AFINN: vergibt Scores von -5 bis 5; negative Werte = negativ, positive Werte = positiv
Bing: ordnet Wörter den Kategorien positiv/negativ zu
NRC: ordnet Wörter einer oder mehreren der zehn Kategorien zu: positive, negative, anger, anticipation, disgust, fear, joy, sadness, surprise, trust
Lege zur Untersuchung ein Data Frame new_sentiments an. Filtere ein Finanzlexikon heraus, erzeuge für AFINN ein binäres (polares) Sentimentfeld, indem du den numerischen Score in positive oder negative überführst, und füge die Anzahl unterschiedlicher Wörter je Lexikon hinzu.
new_sentiments hat eine Spalte mit verschiedenen Sentimentkategorien. Für einen besseren Blick auf Wortzahlen pro Lexikon und Kategorie nutzt du spread() aus tidyr, um die Kategorien zu pivotieren. Verwende die Pakete knitr und kableExtra in deiner Funktion my_kable_styling(). Mit color_tile() und color_bar() aus formattable fügst du Farbe hinzu und erhältst eine ansprechend formatierte Tabelle. Drucke die Tabelle und vergleiche die Lexika.
Die Tabelle vermittelt Größe und Struktur der einzelnen Lexika.
Match: Was überschneidet sich?
Um zu sehen, welches Lexikon besser zu Lyrics passt, betrachtest du den Match-Anteil der Wörter, die in Lexikon und Lyrics vorkommen. Zur Erinnerung: prince_tidy enthält 76.116 Wörter und 7.851 unterschiedliche Wörter.
Wie viele davon sind in den Lexika enthalten?
Führe ein inner_join() zwischen prince_tidy und new_sentiments aus und gruppiere nach Lexikon. Das NRC-Lexikon hat 10 Kategorien, und ein Wort kann in mehreren vorkommen (etwa negativ und traurig). Nutze daher n_distinct() in summarise(), um die Anzahl unterschiedlicher Wörter pro Lexikon zu erhalten.
prince_tidy %>%
mutate(words_in_lyrics = n_distinct(word))%>%
inner_join(new_sentiments)%>%
group_by(lexicon, words_in_lyrics, words_in_lexicon)%>%
summarise(lex_match_words = n_distinct(word))%>%
ungroup()%>%
mutate(total_match_words = sum(lex_match_words),#Not used but good to have
match_ratio = lex_match_words / words_in_lyrics)%>%
select(lexicon, lex_match_words, words_in_lyrics, match_ratio)%>%
mutate(lex_match_words = color_bar("lightpink")(lex_match_words),
lexicon = color_tile("lightgreen","lightgreen")(lexicon))%>%
my_kable_styling(caption ="Lyrics Found In Lexicons")Bereitgestellt von
Lyrics Found In Lexicons
lexicon
lex_match_words
words_in_lyrics
match_ratio
AFINN
770
7851
0.0980767
bing
1185
7851
0.1509362
nrc
1678
7851
0.2137307
NRC enthält mehr unterschiedliche Wörter aus den Lyrics als AFINN oder Bing. Beachte, dass die Summe der Match-Ratios gering ist. Kein Lexikon deckt alle Wörter ab — und sollte es auch nicht. Viele Wörter sind neutral und haben kein Sentiment. 2000 etwa ist normalerweise neutral und daher nicht im Lexikon. Dennoch war das Jahr mit Ängsten belegt (Y2K), was im Song mitschwingen kann — typische Lexika erfassen das nicht.
Warum fehlen Wörter in Lexika?
Nicht jedes Wort trägt ein Sentiment.
Lexika wurden für andere Textsorten erstellt — nicht für Lyrics.
Die konkrete Form fehlt: strong kann enthalten sein, strongly nicht. Möglicherweise brauchst du mehr Cleaning! (Dazu später mehr.)
Glaub’s nicht nur aufs Wort
Schau dir ein paar spezifische Wörter aus Prince’ Lyrics an, die das Sentiment beeinflussen könnten. Stehen sie in allen Lexika?
new_sentiments %>%
filter(word %in% c("dark","controversy","gangster","discouraged","race"))%>%
arrange(word)%>%#sort
select(-score)%>%#remove this field
mutate(word = color_tile("lightblue","lightblue")(word),
words_in_lexicon = color_bar("lightpink")(words_in_lexicon),
lexicon = color_tile("lightgreen","lightgreen")(lexicon))%>%
my_kable_styling(caption ="Specific Words")Bereitgestellt von
Specific Words
word
sentiment
lexicon
words_in_lexicon
controversy
negative
nrc
6468
controversy
negative
bing
6785
dark
sadness
nrc
6468
dark
negative
bing
6785
discouraged
negative
AFINN
2476
gangster
negative
bing
6785
Controversy und dark erscheinen in NRC und Bing, gangster nur in Bing. Race fehlt komplett — dabei ist es zentral in Prince’ Lyrics. Lässt es sich klar einem Sentiment zuordnen? AFINN ist deutlich kleiner und enthält nur eines dieser Wörter.
Wortformen
Jetzt wird’s kniffliger. Sexualität ist ein häufiges Thema bei Prince. Wie beeinflussen unterschiedliche Wortformen die lexikonbasierte Sentimentanalyse? Hier alle Verwendungen des Wortstamms sex in den Lyrics. Vergleiche mit Bing und NRC und sieh dir die Matches an.
my_word_list <- prince_data %>%
unnest_tokens(word, lyrics)%>%
filter(grepl("sex", word))%>%#Use `grepl()` to find the substring `"sex"`
count(word)%>%
select(myword = word, n)%>%#Rename word
arrange(desc(n))
new_sentiments %>%#Right join gets all words in `my_word_list` to show nulls
right_join(my_word_list, by = c("word"="myword"))%>%
filter(word %in% my_word_list$myword)%>%
mutate(word = color_tile("lightblue","lightblue")(word),
instances = color_tile("lightpink","lightpink")(n),
lexicon = color_tile("lightgreen","lightgreen")(lexicon))%>%
select(-score,-n)%>%#Remove these fields
my_kable_styling(caption ="Dependency on Word Form")Bereitgestellt von
Dependency on Word Form
word
sentiment
lexicon
words_in_lexicon
instances
sexy
positive
bing
6785
220
sexy
positive
AFINN
2476
220
sex
anticipation
nrc
6468
185
sex
joy
nrc
6468
185
sex
positive
nrc
6468
185
sex
trust
nrc
6468
185
superfunkycalifragisexy
NA
NA
NA
19
lovesexy
NA
NA
NA
16
sexual
NA
NA
NA
11
sexuality
NA
NA
NA
11
sexiness
NA
NA
NA
2
sexually
NA
NA
NA
2
sexe
NA
NA
NA
1
sexed
NA
NA
NA
1
sexier
NA
NA
NA
1
superfunkycalifraagisexy
NA
NA
NA
1
superfunkycalifragisexi
NA
NA
NA
1
Auffällig: sexy kommt häufig vor, fehlt aber in dieser Form im NRC. sex findet NRC, Bing hingegen nicht. Würde es helfen, Stämme oder Wurzeln zu betrachten? Dein Text kann Vergangenheitsformen, Plurale oder Adverbien eines Wortstamms enthalten, die in keinem Lexikon stehen. Wie gehst du damit um?
Mehr Datenvorbereitung?
Eventuell brauchst du weitere Vorbereitungsschritte. Drei Techniken, die du in Betracht ziehen solltest:
Stemming: Entfernen von Suffixen, um den gemeinsamen Ursprung zu erhalten
Lemmatisierung: Zurückführen flektierter/abgeleiteter Wörter auf Stamm-, Grund- oder Basisform
Wortersatz: Wörter durch häufigere Synonyme ersetzen
Fortgeschritten ist der Einsatz von Synonymen (semantisch ähnlichen Wörtern) und Hypernymen (übergeordneten Begriffen). Diese werden häufiger verwendet als das Wort im Song und tauchen im Lexikon auf — der Match-Anteil steigt. Für dieses Tutorial sprengt das den Rahmen, aber es lohnt sich!
Challenge: Recherchiere, wie Lexika entstehen. Gibt es eines, das sich besser für Lyrics eignet? Wenn dich das Thema packt: Was bräuchtest du, um ein eigenes Lexikon zu bauen? Und worin unterscheidet sich klassifikatorbasierte von lexikonbasierter Sentimentanalyse?
Detaillierte Analyse
Mit dem Grundlagenwissen zu Datensatz und Lexika kannst du beide für Analysen zusammenführen. Die Schritte:
Lexikon-spezifische Datensätze erstellen
Polares Sentiment über alle Songs betrachten
Sentimentverlauf über die Zeit untersuchen
Ergebnisse mit Ereignissen aus Prince’ Leben abgleichen
Sentiment auf Song-Ebene analysieren
Untersuchen, wie Wortpaare Sentiment beeinflussen
Sentiment-Datensätze erstellen
Erzeuge für jedes Lexikon einen Prince-Sentiment-Datensatz, indem du per inner_join() gegen get_sentiments() joinst. Übergib jeweils den Lexikon-Namen. Für diese Übung: Bing für binäre und NRC für kategoriale Sentiments. Da Wörter im NRC in mehreren Kategorien stehen können (z. B. Negative/Fear oder Positive/Joy), erzeugst du zusätzlich einen Subset ohne positive und negative Kategorien.
prince_bing <- prince_tidy %>%
inner_join(get_sentiments("bing"))Bereitgestellt von
prince_nrc <- prince_tidy %>%
inner_join(get_sentiments("nrc"))Bereitgestellt von
prince_nrc_sub <- prince_tidy %>%
inner_join(get_sentiments("nrc"))%>%
filter(!sentiment %in% c("positive","negative"))Bereitgestellt von
In the Mood: Gesamtsentiment
Für die Detailanalyse willst du verschiedene Ebenen betrachten: alle Songs, Chart-Level, Jahrzehnt und Wort. Starte mit der NRC-Sentimentanalyse des gesamten Datensatzes.
(Nur zum Spaß: Mit den Paketen memery und magick lassen sich Bilder (Memes) in Plots einbetten.)
nrc_plot <- prince_nrc %>%
group_by(sentiment)%>%
summarise(word_count = n())%>%
ungroup()%>%
mutate(sentiment = reorder(sentiment, word_count))%>%#Use `fill = -word_count` to make the larger bars darker
ggplot(aes(sentiment, word_count, fill =-word_count))+
geom_col()+
guides(fill =FALSE)+#Turn off the legend
theme_lyrics()+
labs(x =NULL, y ="Word Count")+
scale_y_continuous(limits = c(0,15000))+#Hard code the axis limit
ggtitle("Prince NRC Sentiment")+
coord_flip()
img <-"prince_background2.jpg"#Load the background image
lab <-""#Turn off the label#Overlay the plot on the image and create the meme file
meme(img, lab,"meme_nrc.jpg", inset = nrc_plot)#Read the file back in and display it!
nrc_meme <- image_read("meme_nrc.jpg")
plot(nrc_meme)Bereitgestellt von
Bei Prince’ Lyrics überwiegt im NRC offenbar das Positive. Sind alle Wörter mit disgust oder anger zugleich in der Kategorie negative? Ein Blick könnte sich lohnen.
Jetzt das Bing-Gesamtsentiment. Von den 1.185 unterschiedlichen Wörtern aus Prince’ Lyrics, die im Bing-Lexikon auftauchen: Wie viele sind positiv, wie viele negativ?
Im Bing-Lexikon wirkt es fast ausgeglichen zwischen positiv und negativ. Hebelt sich Gesamtsentiment aus, wenn der Datensatz zu groß ist? Schwer zu sagen — betrachte Teilmengen und vergleiche.
In der Akustik gibt es Phasenauslöschung: Treffen zwei identische Wellen phaseninvertiert aufeinander, löschen sie sich aus — totale Stille auf dieser Frequenz! Was bedeutet das für Sentimentanalysen großer Datensätze? Denk mal darüber nach.
Sprachprofessor: „Im Englischen ergibt eine doppelte Verneinung ein Positiv. Im Russischen bleibt sie negativ. Aber es gibt keine Sprache, in der zwei Positive ein Negativ ergeben." Studierender (sarkastisch): „Ja, klar."
Lauter drehen: Chart-Level
Verfeinere die Analyse auf Chart-Level mit dem Bing-Lexikon. Erstelle einen Plot des polaren Sentiments je Chart-Level. Trenne Sentiments via spread() in Spalten und berechne mit mutate() die Polarity (positive - negative) sowie percent_positive ($positive / total * 100$). Für den Polarity-Plot füge per geom_hline() eine yintercept ein. Platziere beide Plots nebeneinander mit grid.arrange().
prince_polarity_chart <- prince_bing %>%
count(sentiment, chart_level)%>%
spread(sentiment, n, fill =0)%>%
mutate(polarity = positive - negative,
percent_positive = positive /(positive + negative)*100)#Polarity by chart
plot1 <- prince_polarity_chart %>%
ggplot( aes(chart_level, polarity, fill = chart_level))+
geom_col()+
scale_fill_manual(values = my_colors[3:5])+
geom_hline(yintercept =0, color ="red")+
theme_lyrics()+ theme(plot.title = element_text(size =11))+
xlab(NULL)+ ylab(NULL)+
ggtitle("Polarity By Chart Level")#Percent positive by chart
plot2 <- prince_polarity_chart %>%
ggplot( aes(chart_level, percent_positive, fill = chart_level))+
geom_col()+
scale_fill_manual(values = c(my_colors[3:5]))+
geom_hline(yintercept =0, color ="red")+
theme_lyrics()+ theme(plot.title = element_text(size =11))+
xlab(NULL)+ ylab(NULL)+
ggtitle("Percent Positive By Chart Level")
grid.arrange(plot1, plot2, ncol =2)Bereitgestellt von
Ergibt sich hier, dass gechartete Songs tendenziell positiver sind? Falls ja: Was sagt das darüber aus, was das Publikum hören will? Darf man das so annehmen? Relativ zum Gesamtsentiment wirken gechartete Songs leicht positiver. Interessant — zumal das Bing-Lexikon insgesamt mehr negative als positive Wörter enthält.
Schmelzende Pole: So Blue
Wenn du polares Sentiment betrachtest, willst du wissen, ob es sich über die Zeit wettert — äh, ändert (Geek-Humor). Nutze geom_smooth() mit Methode loess für eine weiche Kurve sowie geom_smooth(method = "lm") für eine lineare.
prince_polarity_year <- prince_bing %>%
count(sentiment, year)%>%
spread(sentiment, n, fill =0)%>%
mutate(polarity = positive - negative,
percent_positive = positive /(positive + negative)*100)
polarity_over_time <- prince_polarity_year %>%
ggplot(aes(year, polarity, color = ifelse(polarity >=0,my_colors[5],my_colors[4])))+
geom_col()+
geom_smooth(method ="loess", se =FALSE)+
geom_smooth(method ="lm", se =FALSE, aes(color = my_colors[1]))+
theme_lyrics()+ theme(plot.title = element_text(size =11))+
xlab(NULL)+ ylab(NULL)+
ggtitle("Polarity Over Time")
relative_polarity_over_time <- prince_polarity_year %>%
ggplot(aes(year, percent_positive , color = ifelse(polarity >=0,my_colors[5],my_colors[4])))+
geom_col()+
geom_smooth(method ="loess", se =FALSE)+
geom_smooth(method ="lm", se =FALSE, aes(color = my_colors[1]))+
theme_lyrics()+ theme(plot.title = element_text(size =11))+
xlab(NULL)+ ylab(NULL)+
ggtitle("Percent Positive Over Time")
grid.arrange(polarity_over_time, relative_polarity_over_time, ncol =2)Bereitgestellt von
Einige Extreme wurden im zweiten Plot angepasst, aber der Gesamtrend des polaren Sentiments ist in beiden Fällen negativ.
Stimmungsring
Mit chordDiagram() untersuchen wir die Beziehungen zwischen NRC-Sentiments und Jahrzehnten. Die Sentimentkategorien erscheinen auf der oberen, die Jahrzehnte auf der unteren Ringhälfte.
grid.col = c("1970s"= my_colors[1],"1980s"= my_colors[2],"1990s"= my_colors[3],"2000s"= my_colors[4],"2010s"= my_colors[5],"anger"="grey","anticipation"="grey","disgust"="grey","fear"="grey","joy"="grey","sadness"="grey","surprise"="grey","trust"="grey")
decade_mood <- prince_nrc %>%
filter(decade !="NA"&!sentiment %in% c("positive","negative"))%>%
count(sentiment, decade)%>%
group_by(decade, sentiment)%>%
summarise(sentiment_sum = sum(n))%>%
ungroup()
circos.clear()#Set the gap size
circos.par(gap.after = c(rep(5, length(unique(decade_mood[[1]]))-1),15,
rep(5, length(unique(decade_mood[[2]]))-1),15))
chordDiagram(decade_mood, grid.col = grid.col, transparency =.2)
title("Relationship Between Mood and Decade")Bereitgestellt von
Die Grafik zeigt Wortanzahlen pro NRC-Kategorie und Jahrzehnt. Auf kleiner Fläche viel Information — Beziehungen zwischen Kategorien und Zeit werden sichtbar. Die Diagramme sind extrem anpassbar — von simpel bis tief informativ.
Sentiment in Echtzeit
Nach all den DataCamp-Kursen juckt es dich sicher, Skills in der Praxis anzuwenden. Tun wir das, indem wir unsere Analyse von Prince’ Lyrics über Zeit mit realen Ereignissen verknüpfen. Bitte bleib kritisch: Eine vereinfachte Analyse trifft auf ein komplexes Thema.
Ich habe eine Liste von Lebensereignissen von Prince zusammengestellt (u. a. Rolling Stone, Biography.com). Ich habe öffentlich bekannte Jahre ausgewählt, die in unserem Datensatz mit Release-Daten abgedeckt sind. Lies nun princeEvents.csv ein. Erzeuge mit prince_bing und spread() einen Polaritätswert pro Jahr. Joine auf das Data Frame events und erstelle ein Feld für das Sentiment, damit du die Balken einfärben kannst. Bei langen Textlabels empfiehlt sich coord_flip().
events <- read.csv('princeEvents.csv', stringsAsFactors =FALSE)
year_polarity_bing <- prince_bing %>%
group_by(year, sentiment)%>%
count(year, sentiment)%>%
spread(sentiment, n)%>%
mutate(polarity = positive - negative,
ratio = polarity /(positive + negative))#use polarity ratio in next graph
events %>%#Left join gets event years with no releases
left_join(year_polarity_bing)%>%
filter(event !=" ")%>%#Account for bad data
mutate(event = reorder(event, year),#Sort chart by desc year
sentiment = ifelse(positive > negative,"positive","negative"))%>%
ggplot(aes(event, polarity, fill = sentiment))+
geom_bar(stat ="identity")+
theme_minimal()+ theme(legend.position ="none")+
xlab(NULL)+
ggtitle("Sentiment by Events")+
coord_flip()Bereitgestellt von
Der Vergleich von Ereignissen und Sentiment ist faszinierend. Subjektiv wirken sie sehr korreliert. Klar, ich habe den Ereignisdatensatz selbst erstellt und könnte ihn (unbewusst) an die Lyrics angepasst haben. Ich habe jedoch pro Jahr mehrere Quellen genutzt und mich auf die meistgenannten Fakten gestützt. Die Ergebnisse motivieren, auf der Wortebene tiefer zu graben — worüber sang Prince konkret?
The Black Album: 1994–1996
1994 veröffentlichte Prince The Black Album — ein Versuch, sein afroamerikanisches Publikum zurückzugewinnen. Da 1994 und die beiden Folgejahre in der Analyse herausstechen, betrachtest du die Top-Wörter, die im Zeitraum 1994–1996 im NRC-Lexikon matchen. Nutze geom_label_repel() aus ggrepel zusammen mit geom_point(). Der Plot ist etwas knifflig — probiere ruhig an den Parametern.
plot_words_94_96 <- prince_nrc %>%
filter(year %in% c("1994","1995","1996"))%>%
group_by(sentiment)%>%
count(word, sort =TRUE)%>%
arrange(desc(n))%>%
slice(seq_len(8))%>%#consider top_n() from dplyr also
ungroup()
plot_words_94_96 %>%#Set `y = 1` to just plot one variable and use word as the label
ggplot(aes(word,1, label = word, fill = sentiment ))+#You want the words, not the points
geom_point(color ="transparent")+#Make sure the labels don't overlap
geom_label_repel(force =1,nudge_y =.5,
direction ="y",
box.padding =0.04,
segment.color ="transparent",
size =3)+
facet_grid(~sentiment)+
theme_lyrics()+
theme(axis.text.y = element_blank(), axis.text.x = element_blank(),
axis.title.x = element_text(size =6),
panel.grid = element_blank(), panel.background = element_blank(),
panel.border = element_rect("lightgray", fill =NA),
strip.text.x = element_text(size =9))+
xlab(NULL)+ ylab(NULL)+
ggtitle("1994 - 1996 NRC Sentiment")+
coord_flip()Bereitgestellt von
Prince sagte 1994 im Rolling Stone:
„Wenn du einen Mann am Träumen hinderst, wird er zum Sklaven. Genau da war ich."
Er trat mit dem Wort „slave“ deutlich sichtbar im Gesicht auf — offener Protest gegen sein damaliges Label. Zudem änderte er seinen Namen in ein Symbol. Wie gut passen die oben gezeigten Wörter zu diesen Ereignissen? Siehst du „slave“, „emancipation“, „black“ und „change“?
(Das gilt nicht nur für Prince. Du kannst das mit jedem Text tun! Beispiel: Welche trust-Wörter nutzte Präsident Trump in seinem ersten Amtsjahr am häufigsten?)
Dieses Mal ist es persönlich: 1998
Der Plot Sentiment by Events zeigte: Zwischen 1996 und 1998 heiratete Prince, verlor zwei Kinder und soll auf der Bühne 9/11 angedeutet haben. (Suche auf YouTube nach seinem 1998er Konzert in den Niederlanden!) Nutze die gleichen Schritte und betrachte die Top-10-Wörter je NRC-Kategorie für 1998.
Angesichts persönlicher Verluste und der Bedrohung durch Terror sind Wörter wie „suicide“, „waste“, „mad“, „hurt“ — und viele hier nicht gezeigte — sehr treffend. Sie sind stark und geben echten Einblick in die Stimmung dieser Zeit.
Auf dem Radar: Radar-Charts
Ein weiterer Vergleich über Kategorien gelingt mit Radar- bzw. Spider-Charts. Erstellen kannst du sie mit dem Paket radarchart. Ideal, um ähnliche Ausprägungen oder Ausreißer je Variable zu erkennen.
Wir brechen die Analyse auf drei Ebenen herunter: Jahr, Chart und Jahrzehnt. (Aus Platzgründen zeige ich nur den Code für spezifische Jahre.)
Nutze prince_nrc_sub ohne positive und negative, damit die übrigen Kategorien besser sichtbar sind. Berechne zunächst die Wortanzahl je Sentiment und Jahr sowie die Gesamtanzahl und leite den Prozentwert ab ($Sentimentwörter pro Jahr / Gesamt pro Jahr * 100$).
Filtere auf die Jahre 1978, 1994, 1995 und entferne Unnötiges mit select().
Pivotiere mit spread() Jahr und Prozentwerte in Spalten, sodass je Sentiment eine Zeile und je Jahr eine Spalte entsteht. Erzeuge dann mit chartJSRadar() ein interaktives Widget. Du kannst Labels im Mouseover anzeigen lassen. (FYI: Manchmal kappt radarchart das J und Y von „joy“ — sieht dann wie „iov“ aus.)
#Get the count of words per sentiment per year
year_sentiment_nrc <- prince_nrc_sub %>%
group_by(year, sentiment)%>%
count(year, sentiment)%>%
select(year, sentiment, sentiment_year_count = n)#Get the total count of sentiment words per year (not distinct)
total_sentiment_year <- prince_nrc_sub %>%
count(year)%>%
select(year, year_total = n)#Join the two and create a percent field
year_radar_chart <- year_sentiment_nrc %>%
inner_join(total_sentiment_year, by ="year")%>%
mutate(percent = sentiment_year_count / year_total *100)%>%
filter(year %in% c("1978","1994","1995"))%>%
select(-sentiment_year_count,-year_total)%>%
spread(year, percent)%>%
chartJSRadar(showToolTipLabel =TRUE,
main ="NRC Years Radar")Bereitgestellt von
Du musst etwas scrollen, um die Plots zu vergleichen — dafür bleiben sie gut lesbar. Bemerkenswert: Bei kleineren Datensätzen wie year erkennst du Varianzen pro Sentiment deutlicher. Zudem fällt auf, dass im Prozentvergleich „joy“ überall am höchsten liegt (besonders zu Beginn von Prince’ Karriere 1978).
Sign O' the Times
Spotify nutzt Machine Learning für Empfehlungen an 140 Millionen aktive Nutzer:innen. Lyrik-Inhalte fließen bisher nicht ein. Stell dir vor, sie täten es … Werfen wir einen tieferen Blick in die Stimmung einzelner Songs.
1987 schrieb Prince „Sign O' the Times“. Ein Billboard-Artikel von Kenneth Partridge sagt:
„Er greift Themen wie AIDS, Gangs, Drogen, Naturkatastrophen und sogar die Challenger-Explosion auf, bleibt dabei aber cool und distanziert. 'Sign O' the Times' ist ein Status-Update, kein Aufruf zum Handeln. Seine Lösung ist weder der Marsch auf die Straße noch der Anruf beim Abgeordneten."
Wie würde eine Maschine die Stimmung des Songs interpretieren? Hoch emotional oder eher informativ? Spiegelt er Prince als Person oder die Gesellschaft? Plotte die NRC-Kategorien mit ggplot2.
prince_nrc %>%
filter(song %in%"sign o the times")%>%
group_by(sentiment)%>%
summarise(word_count = n())%>%
ungroup()%>%
mutate(sentiment = reorder(sentiment, word_count))%>%
ggplot(aes(sentiment, word_count, fill =-word_count))+
geom_col()+
guides(fill =FALSE)+
theme_minimal()+ theme_lyrics()+
labs(x =NULL, y ="Word Count")+
ggtitle("Sign O' The Times NRC Sentiment")+
coord_flip()Bereitgestellt von
Subjektiv, aber die Ergebnisse scheinen Partridge zu bestätigen: Prince „bleibt cool und distanziert“. Dafür spricht, dass anticipation-Wörter häufiger sind als emotionale Kategorien wie sadness, fear und anger. Dieser Artikel ordnet die drei als Emotionen ein, anticipation hingegen als Sentiment. Der Billboard-Autor bezeichnet den Song als „Status-Update, fast ohne Emotion“. Die Maschine scheint zuzustimmen. Siehst du das auch so? Sentimentanalyse ist nicht rein technisch — sie gilt als Schnittstelle, wo KI auf Psychologie trifft.
Mit ggplot2 als leicht veränderter Darstellung betrachten wir die Wörter je Kategorie.
prince_tidy %>%
filter(song %in%'sign o the times')%>%
distinct(word)%>%
inner_join(get_sentiments("nrc"))%>%
ggplot(aes(x = word, fill = sentiment))+
facet_grid(~sentiment)+
geom_bar()+#Create a bar for each word per sentiment
theme_lyrics()+
theme(panel.grid.major.x = element_blank(),
axis.text.x = element_blank())+#Place the words on the y-axis
xlab(NULL)+ ylab(NULL)+
ggtitle("Sign O' The Times Sentiment Words")+
coord_flip()Bereitgestellt von
Kannst du die Wörter den Themen aus dem Billboard-Artikel zuordnen? Challenger-Katastrophe → „rocket“, AIDS → „disease“, Drogen → „crack“, Naturkatastrophen → „hurricane“, Gangs → „gang“ usw.
Fast so, als hätte Partridge vorher R-Code geschrieben und diesen Plot erzeugt!
Weitere Songs
Betrachte Sentimentkategorien für einige weitere Songs mit markanten Titeln und prüfe, ob es Korrelationen gibt.
prince_nrc_sub %>%
filter(song %in% c("so blue","controversy","raspberry beret","when doves cry","the future","1999"))%>%
count(song, sentiment, year)%>%
mutate(sentiment = reorder(sentiment, n), song = reorder(song, n))%>%
ggplot(aes(sentiment, n, fill = sentiment))+
geom_col()+
facet_wrap(year ~ song, scales ="free_x", labeller = label_both)+
theme_lyrics()+
theme(panel.grid.major.x = element_blank(),
axis.text.x = element_blank())+
labs(x =NULL, y =NULL)+
ggtitle("NRC Sentiment Song Analysis")+
coord_flip()Bereitgestellt von
NRC zeigt hohe anticipation und fear für einen Song über the future sowie zusätzlich hohe trust bei controversy. Ausgeprägt traurige Songs scheinen ihren Titeln zu entsprechen.
Bigrams pro Jahrzehnt
Bisher hast du Unigramme (einzelne Wörter) betrachtet. Aber wenn „love“ häufig ist: Was steht davor? Oder danach? Einzelwörter ohne Kontext können in die Irre führen. Zeit für Bigramme, also Wortpaare.
Praktisch: tidytext kann auch Wortpaare entpacken. Rufe unnest_tokens() mit token = ngrams auf. Da wir Bigramme betrachten, setze n = 2. Speichere die Ergebnisse in prince_bigrams.
Mit tidyr kannst du Bigramme mittels separate() in einzelne Wörter zerlegen. Um Stopp- und unerwünschte Wörter zu entfernen, trennst du das Bigramm, filterst Unerwünschtes heraus und setzt die Paare per unite() wieder zusammen. So visualisierst du die häufigsten Bigramme pro Jahrzehnt. (Teil Eins erklärt slice() und row_number().)
prince_bigrams <- prince_data %>%
unnest_tokens(bigram, lyrics, token ="ngrams", n =2)
bigrams_separated <- prince_bigrams %>%
separate(bigram, c("word1","word2"), sep =" ")
bigrams_filtered <- bigrams_separated %>%
filter(!word1 %in% stop_words$word)%>%
filter(!word2 %in% stop_words$word)%>%
filter(!word1 %in% undesirable_words)%>%
filter(!word2 %in% undesirable_words)#Because there is so much repetition in music, also filter out the cases where the two words are the same
bigram_decade <- bigrams_filtered %>%
filter(word1 != word2)%>%
filter(decade !="NA")%>%
unite(bigram, word1, word2, sep =" ")%>%
inner_join(prince_data)%>%
count(bigram, decade, sort =TRUE)%>%
group_by(decade)%>%
slice(seq_len(7))%>%
ungroup()%>%
arrange(decade, n)%>%
mutate(row = row_number())Bereitgestellt von
## Joining, by = c("song", "year", "album", "peak", "us_pop", "us_rnb", "decade", "chart_level", "charted")Bereitgestellt von
bigram_decade %>%
ggplot(aes(row, n, fill = decade))+
geom_col(show.legend =FALSE)+
facet_wrap(~decade, scales ="free_y")+
xlab(NULL)+ ylab(NULL)+
scale_x_continuous(# This handles replacement of row
breaks = bigram_decade$row,# Notice need to reuse data frame
labels = bigram_decade$bigram)+
theme_lyrics()+
theme(panel.grid.major.x = element_blank())+
ggtitle("Bigrams Per Decade")+
coord_flip()Bereitgestellt von
Über Bigramme erkennst du einen Wechsel gängiger Phrasen: von Sex, Tanz und Romantik hin zu Religion und (Rainbow) Children. „Rainbow Baby“ nennen Eltern manchmal ein Kind, das nach einer Fehlgeburt geboren wird. Interessanterweise fand ich keine ernsthafte Rezension zu Prince’ „Rainbow Children“-Album, die das erwähnt.
Sentiment mit Bigrammen
Wie beeinflussen Bigramme das Sentiment? Nutze das AFINN-Lexikon, um Sentiment bei Wortpaaren zu analysieren — speziell wenn Sentiment-Wörter durch „not“ oder andere Negationen relativiert werden.
AFINN <- get_sentiments("afinn")
not_words <- bigrams_separated %>%
filter(word1 =="not")%>%
inner_join(AFINN, by = c(word2 ="word"))%>%
count(word2, score, sort =TRUE)%>%
ungroup()
not_words %>%
mutate(contribution = n * score)%>%
arrange(desc(abs(contribution)))%>%
head(20)%>%
mutate(word2 = reorder(word2, contribution))%>%
ggplot(aes(word2, n * score, fill = n * score >0))+
geom_col(show.legend =FALSE)+
theme_lyrics()+
xlab("Words preceded by \"not\"")+
ylab("Sentiment score * Number of Occurrences")+
ggtitle("Polar Sentiment of Words Preceded by Not")+
coord_flip()Bereitgestellt von
In der ersten Zeile erhält care ein false positive, weil das „not“ bei Unigramm-Analyse ignoriert wird. Heben sich falsche Positive und falsche Negative gegenseitig auf?
Auch das bleibt eine offene Frage — ein gutes Thema für weitere Untersuchungen. Tipp: Mit token = "trigrams" und n = 3 in unnest_tokens() kannst du drei aufeinanderfolgende Wörter analysieren!
Weitere Negationswörter solltest du ebenfalls betrachten. Erstelle nun ein Netzwerkdiagramm mit ggraph und igraph. Ordne die Wörter als verbundene Knoten an, mit den Negationswörtern im Zentrum. Erzeuge das erste Objekt aus dem tidy Datensatz mit graph_from_data_frame() und plottet mit ggraph(). geom_edge_density() hebt wichtige Knoten hervor. Ein ähnliches Beispiel findest du im Buch von Julia Silge und David Robinson zu Tidy Text Mining.
Hier siehst du Wortpaare rund um Negationswörter. Wenn in einer Unigramm-Analyse „alone“ negativ wirkt, kehrt das Bigramm „not alone“ den Effekt um. Manche Wörter verbinden mehrere Knoten — gut sichtbar: „never hurt“ und „not hurt“.
Paarweise Vergleiche
Nachdem du n-Gramme gesehen hast, wirf einen Blick auf Korrelationen zwischen Wörtern. Welche Wörter sind am stärksten korreliert? Nutze pairwise_count() aus widyr für Co-Occurrence-Counts — also: Wie oft treten Wortpaare gemeinsam in einem Song auf? widyr arbeitet mit tidy Datensätzen, weitet sie vorübergehend und gibt sie tidy zurück.
Zur Vereinfachung habe ich vier markante Wörter aus Prince’ Lyrics gewählt.
pwc <- prince_tidy %>%
filter(n()>=20)%>%#High counts
pairwise_count(word, song, sort =TRUE)%>%
filter(item1 %in% c("love","peace","gangster","hate"))%>%
group_by(item1)%>%
slice(seq_len(7))%>%
ungroup()%>%
mutate(row =-row_number())#Descending order
pwc %>%
ggplot(aes(row, n, fill = item1))+
geom_bar(stat ="identity", show.legend =FALSE)+
facet_wrap(~item1, scales ="free")+
scale_x_continuous(#This handles replacement of row
breaks = pwc$row,#Notice need to reuse data frame
labels = pwc$item2)+
theme_lyrics()+ theme(panel.grid.major.x = element_blank())+
xlab(NULL)+ ylab(NULL)+
ggtitle("Pairwise Counts")+
coord_flip()Bereitgestellt von
Im Vergleich dazu die paarweise Korrelation — wie oft Wörter gemeinsam auftreten relativ zu ihrem Einzelauftreten. pairwise_cor() bestimmt Korrelationen basierend auf Co-Occurrence in denselben Songs.
Schon diese vier Wörter liefern spannende Einblicke! Es zeichnen sich Themen ab — perfekte Überleitung zum nächsten Tutorial über Topic Modeling.
Fazit
In diesem Tutorial hast du ein tidy Data Set erstellt und Basisinformationen analysiert: lexikalische Diversität, Songanzahl pro Veröffentlichungsjahr und den Zusammenhang zwischen Jahrzehnt und Charterfolg. Du hast Sentiment-Lexika untersucht und ihre Passung zu den Lyrics bewertet. Danach folgten Sentimentanalysen über alle Songs, über die Zeit, auf Song-Ebene und mit Blick auf Bigramme — visualisiert mit vielfältigen, aussagekräftigen Plots.
Ist es möglich, ein Programm zu schreiben, das die Stimmung in Lyrics erkennt? Ja! Wie verlässlich sind die Ergebnisse? Das hängt von vielen Faktoren ab: Datenvorbereitung, Lexikonwahl, Methode, Datenqualität usw. Der Abgleich mit realen Ereignissen — persönlich und gesellschaftlich — kann die Stimmung eines Liedtexts erhellen. Princes polares Sentiment scheint über die Zeit leicht zu sinken, dennoch sticht joy insgesamt hervor. Gechartete Songs wirken positiver als ungechartete. Die Behauptungen rund um 9/11 und seinen Tod scheinen auf unheimliche Weise zu seinen Worten zu passen.
Aber Lyrics sind komplex, und zu viele Annahmen sind gefährlich. Wenn du die hier gewonnenen Erkenntnisse in das nächste Tutorial, Teil Zwei-B zu Topic Modeling, mitnimmst, bist du auf dem besten Weg, Themen und Motive herauszuarbeiten. Wie viel NLP ist bei Lyrics ohne Interpunktion oder Satzstruktur möglich? Wovon hat Prince wirklich gesungen? Jetzt, da du die Stimmung kennst, bist du bereit, potenzielle Themen zu untersuchen. Und schließlich: Lassen sich Jahrzehnt oder Chart-Level eines Songs vorhersagen? Finde es in den nächsten Tutorials heraus!
Ich hoffe, dir hat die Reise bisher gefallen. In Teil Zwei-B feiern wir „wie 1999“!
Gewinne Vertrauen in die OLS-Regression, indem du ihre theoretischen Grundlagen beherrschst. Erforsche, wie du einfache Implementierungen in Excel, R und Python durchführst.
Lerne, wie du Python-Listen mit Techniken wie Slicing, List Comprehensions und itertools aufteilen kannst. Finde heraus, wann du welche Methode für die beste Datenverarbeitung nutzen solltest.
Erforsche Pythons match-case: eine Anleitung zu seiner Syntax, Anwendungen in Data Science und ML sowie eine vergleichende Analyse mit dem traditionellen switch-case.
Mach dich mit Python-Datenstrukturen vertraut: Lerne mehr über Datentypen und primitive sowie nicht-primitive Datenstrukturen wie Strings, Listen, Stapel usw.
Begib dich auf eine emotionale Reise durch Leben und Werk von Prince, The Artist — Teil Zwei-A einer dreiteiligen Tutorialserie, in der wir mithilfe von Sentimentanalyse mit R Einblicke in die Karriere und den gesellschaftlichen Einfluss des Künstlers gewinnen. Die drei Tutorials behandeln:
Wenn du mehr über Sentimentanalyse lernen möchtest, wirf einen Blick auf unseren Kurs Sentiment Analysis in R: The Tidy Way.
Wusstest du, dass Prince angeblich schon drei Jahre vor 9/11 auf der Bühne darauf anspielte? Vielleicht sagte er auch seinen eigenen Tod voraus. Stimmt das? Wäre es nicht spannend, seine Songtexte zu untersuchen und dir selbst ein fundiertes Urteil zu bilden? Am 21. April 1985 nahm er "Sometimes It Snows In April" auf. Genau 31 Jahre später, am 21. April 2016, starb er. Schau dir seine Wortwahl in der Sentimentanalyse dieses Songs an:
Das gibt Predictive Analytics eine ganz neue Bedeutung!
Aufbau
Auf jeden Codeabschnitt folgt eine — meist subjektive — Interpretation. Du kannst gerne zwischen den Abschnitten springen und eigene Annahmen treffen! Ich empfehle die folgende Einführung zu lesen. Wenn du direkt in den Code einsteigen möchtest, klicke hier. Wenn dir Lexika bereits vertraut sind, nimm die Abkürzung direkt zum Abschnitt mit der detaillierten Analyse.
Einführung
Triplets: Drei-Teile-Zusammenfassung
Ich habe gerade zwei Kätzchen und einen Welpen gerettet, daher denke ich jetzt in Dreiergruppen. Außerdem besagt die Regel der Drei, dass Dinge in Dreierpackung oft witziger, befriedigender oder wirkungsvoller sind — deshalb habe ich das Tutorial in drei Teile aufgeteilt. In Teil Eins hast du Text Mining und explorative Analysen anhand eines Datensatzes mit Hunderten Songtexten des legendären Prince kennengelernt. In Teil Zwei fokussierst du Sentimentanalyse (Zwei-A) und Topic Modeling (Zwei-B) mit NLP. Teil Drei schließt mit weiteren Predictive-Analytics-Aufgaben via Machine Learning: etwa, ob sich das Veröffentlichungsjahrzehnt eines Songs bestimmen lässt oder — spannender — ob sich anhand der Lyrics der Einzug in die Billboard-Charts vorhersagen lässt. In allen Tutorials nutzt du unterschiedliche Machine-Learning-Algorithmen — im folgenden Schaubild jeweils rot markiert.
Trennung fällt schwer
Ja, ich habe drei Teile versprochen. Aber Teil Zwei habe ich in Zwei-A (Sentimentanalyse) und Zwei-B (Topic Modeling – folgt in Kürze) gesplittet, also sind es am Ende vier Tutorials. Es gibt viele starke Artikel und Kurse zur Methodik der Sentimentanalyse. Ziel von Teil Zwei-A ist daher, den Fokus auf Erkenntnisse aus der Analyse zu legen — nicht nur auf den Code. Wir nutzen Prince’ Lyrics als Beispiel, aber die Schritte lassen sich auf jede:n Lieblingskünstler:in übertragen. Die Serie soll Neugier auf die Möglichkeiten der Lyrikanalyse wecken — und nebenbei in Natural Language Processing (NLP) und Machine Learning einführen.
Voraussetzungen
Für Teil Zwei-A brauchst du Grundkenntnisse zu tidy Data — insbesondere Pakete wie
dplyrfür Transformationen,ggplot2für Visualisierungen und den%>%-Pipe-Operator (ursprünglich ausmagrittr). Du wirst sehen, dass mehrere Schritte oft per%>%kompakt und wiederverwendbar verknüpft werden.Da dieses Tutorial auch eine Fallstudie ist, ist es reich an Visualisierungen über die üblichen Diagramme hinaus. Du solltest daher in deiner Umgebung neue Pakete installieren können. Um den Fokus auf Erkenntnisse zu legen, habe ich Code beigefügt, den du auf eigene Datensätze anwenden kannst; die Detailerklärung erfordert stellenweise eigene Recherche. Zu jedem Paket gibt es unterstützende Links. So lernst du neue Tools kennen und konzentrierst dich gleichzeitig auf deren Anwendung.
Tipp: Lies zuerst Teil Eins, um eine Grundlage für die Serie und einen Einblick in das dichte und vielfältige Vokabular von Prince’ Lyrics zu bekommen.
Überblick Sentimentanalyse
Methoden: Sentimentanalyse ist eine Form des Text Minings, die Meinung und Subjektivität eines Textes bestimmen will. Bei Lyrics spiegeln die Ergebnisse nicht nur die Haltung der Künstler:innen, sondern auch kulturelle Strömungen. Es gibt verschiedene Ansätze: Modelle auf Basis trainierter Datensätze, regelbasierte Klassifikatoren und vordefinierte lexikalische Wörterbücher (Lexika). In diesem Tutorial verwenden wir den lexikonbasierten Ansatz — ich empfehle dir aber, auch die anderen Methoden und ihre Trade-offs zu erkunden.
Ebenen: Neben verschiedenen Methoden gibt es Analyse-Ebenen abhängig vom Text: Dokument, Satz und Wort. Bei Lyrics kann ein Dokument etwa das Sentiment pro Jahrzehnt, Jahr, Chart-Level oder Song sein. Die Satzebene ist bei Lyrics selten sinnvoll, da Interpunktion zugunsten von Reim und Rhythmus weggelassen wird. Die Wortebene liefert Details und dient als Grundlage für fortgeschrittene Methoden wie Topic Modeling.
Formuliere deine Fragen!
Jedes Data-Science-Projekt braucht Leitfragen. Behalte beim Durcharbeiten im Hinterkopf: Lässt sich ein Programm schreiben, das die Stimmung eines Liedtexts erkennt? Reichen vordefinierte Lexika aus? Wie viel Datenvorbereitung ist nötig? Lassen sich Ergebnisse mit realen Ereignissen verknüpfen? Ändert sich Sentiment über die Zeit? Sind Hits positiver oder negativer als Nicht-Chart-Songs? Welche Wörter stechen in dem Jahr hervor, in dem Prince 9/11 vorhergesagt haben soll? Hat er seinen eigenen Tod vorhergesagt?
Tipp: Lyrikanalyse ist anders — und oft komplexer — als Reden oder Bücher. Einsichten sind schwerer zu greifen. Sei also vorsichtig mit Annahmen! Ich stelle in diesem Tutorial mehr Fragen als Antworten — denk außerhalb des Quadrats!
Vorbereitung
Libraries und Funktionen
Um mit der Analyse von Prince’ Lyrics zu starten, lade die folgenden Libraries. Das wirkt viel, aber die meisten sind für Grafiken. Da wir viele Visuals nutzen, ist eine einheitliche Farbpalette hilfreich. Ich habe eine Liste mit speziellen ANSI-Farbcodes erstellt. Die
theme()-Funktion ausggplot2erlaubt individuelles Styling, daher erstellen wir eine eigene Funktiontheme_lyrics(), die Default-Einstellungen anpasst.knitrist ein Motor für dynamische Berichte mit R. Zusammen mitkableExtraundformattableerzeugst du ansprechende farbige Tabellen. Erstelle zudemmy_kable_styling(), um die Ausgabe dieser Libraries zu standardisieren. Weitere Pakete erkläre ich, wenn wir sie einsetzen.Sushi-Daten
Wie immer startest du damit, die Rohdaten in ein Data Frame einzulesen. In Teil Eins hast du den ursprünglichen Datensatz aufbereitet: Kontraktionen erweitert, Escape-Sequenzen entfernt, Text kleingeschrieben. Den bereinigten Datensatz hast du als CSV gespeichert. Nutze
read.csv(), umprince_datazu erstellen — die Lyrics sollen Zeichenketten bleiben, also setzestringsAsFactors = FALSE. Da du keine Zeilennummern brauchst, setzerow.names = 1.Hinweis: Du könntest diese Parameter vermeiden, indem du als modernes Data Frame ein tibble via
read_csv()einliest. Aus Konsistenzgründen mit Teil Eins verwenden wir hier aberread.csv().Verschaff dir kurz einen Überblick über die Daten:
Du siehst:
prince_dataumfasst 824 Songs und 10 Spalten. Ein Record ist also tatsächlich ein Song!Sauber aufbereitet:
prince_tidyEin paar verbleibende Wrangling-Schritte:
Um Rohdaten in ein tidy Format zu bringen, nutzt du
unnest_tokens()austidytextund erstellstprince_tidy— ein Wort pro Zeile inklusive zugehörigem Song. Dann helfenanti_join()undfilter()ausdplyrbeim weiteren Säubern. (Details findest du in Teil Eins.)prince_tidyliegt nun tokenisiert vor: ein Wort pro Zeile inklusive zugehörigem Song. Das Data Frame umfasst 76.116 Wörter und 10 Spalten.Deskriptive Statistiken
Falls du Teil Eins nicht gelesen hast, lohnt ein kurzer Blick auf ein paar Übersichtsplots des gesamten Datensatzes. Wir nutzen kreative Grafiken aus
ggplot2,circlizeundyarrr.Alles klar Schiff: Wortanzahl pro Song
Ein Pirat sagt shipshape, wenn alles ordentlich und sauber ist. Hier siehst du die aufgeräumten Daten aus einer spannenden Perspektive: die lexikalische Diversität, sprich der Wortschatz der Lyrics über die Zeit. Ein Pirate Plot ist eine fortgeschrittene Darstellung einer stetigen abhängigen Variable (z. B. Wortanzahl) als Funktion einer kategorialen unabhängigen Variable (z. B. Jahrzehnt). Er kombiniert Rohdatenpunkte, deskriptive und inferenzstatistische Informationen in einem Plot. Details zu
pirateplot()ausyarrrfindest du in diesem Blog.Erstelle das Data Frame
word_summary, das die Anzahl unterschiedlicher Wörter pro Song berechnet. Je vielfältiger die Lyrics, desto größer der Wortschatz. So denkst du dich in die Wortebene ein. Setze für Songs ohne Veröffentlichungsjahr das Felddecadeauf"NONE"und vergib mitselect()klarere Labels.Jeder farbige Punkt steht für einen Song. Der dichte rote Bereich mit dem Wert
"NONE" zeigt: Viele Songs im Datensatz haben kein Veröffentlichungsdatum. In den frühen Jahrzehnten ist ein leichter Aufwärtstrend bei der Anzahl einzigartiger Wörter pro Song zu erkennen. Die durchgezogene Linie markiert den Mittelwert pro Jahrzehnt. Das ist wichtig, wenn du das Sentiment über die Zeit analysierst. Princes Wortwahl wird im Verlauf seiner Karriere aussagekräftiger.Ich empfehle dir, Pirate Plots weiter zu erkunden — wir kratzen hier nur an der Oberfläche!
Rund ums Jahr: Songs pro Jahr
Runde Diagramme eignen sich, um komplexe (oder simple!) Zusammenhänge über viele Kategorien zu zeigen. (Und: Alben waren mal rund!). Der folgende Plot ist ein kreisförmiges Balkendiagramm mit
coord_polar()ausggplot2und zeigt die relative Anzahl der Songs pro Jahr. Der Code ist umfangreich für eine einfache Grafik — aber es lohnt sich. Ein ähnliches Beispiel findest du detailliert in der R Graph Gallery hier. Wie bei Pirate Plots ist dies nur ein Einstieg in zirkuläre Plots.Siehst du die Lücke ganz oben? Das deutet — wie beim Pirate Plot — auf die vielen Songs ohne Veröffentlichungsdatum hin. Bei so vielen unveröffentlichten Songs wäre der Plot mit ihnen kaum lesbar. Fehlende Jahre markieren Jahre ohne Releases. Am produktivsten waren 1996 und 1998.
Warum? Lies weiter!
Akkorde: Chart-Erfolge nach Jahrzehnt
Die nächste Grafik zeigt den Zusammenhang zwischen Veröffentlichungsjahrzehnt und Chart-Erfolg in den Billboard-Charts. Ein
chordDiagram()für Musik — passt doch! Das Tool stammt aus dem elegantencirclize-Paket von Zuguang Gu. Der Ring ist in zwei Bereiche geteilt: Chart-Status (oben) und Jahrzehnt (unten). Breite Lücken trennen die Kategorien, kleinere die Ausprägungen. Ausführlichere Erläuterungen zum Code findest du hier.Auf den ersten Blick komplex, zeigt der Kreis dennoch klar die Anzahl der Songs je Jahrzehnt und Chart-Level. Prince startete in den 1970ern mit wenigen Releases, von denen einige charteten. Vergleiche die 1980er mit den 1990ern: In den 1990ern gab es mehr Releases, in den 1980ern aber mehr Chart-Erfolge. In den 2000ern waren nur wenige Songs kommerziell erfolgreich, in den 2010ern gab es keine Hits.
Lexika und Lyrics
In diesem Abschnitt wirst du:
Sentiment-Lexika erkunden
Das Paket
tidytextenthält den Datensatzsentimentsmit mehreren Lexika — das sind Wörterbücher, die Wörtern Kategorien oder Werte zuordnen.tidytextliefert drei allgemeine Lexika:Lege zur Untersuchung ein Data Frame
new_sentimentsan. Filtere ein Finanzlexikon heraus, erzeuge für AFINN ein binäres (polares) Sentimentfeld, indem du den numerischen Score in positive oder negative überführst, und füge die Anzahl unterschiedlicher Wörter je Lexikon hinzu.new_sentimentshat eine Spalte mit verschiedenen Sentimentkategorien. Für einen besseren Blick auf Wortzahlen pro Lexikon und Kategorie nutzt duspread()austidyr, um die Kategorien zu pivotieren. Verwende die PaketeknitrundkableExtrain deiner Funktionmy_kable_styling(). Mitcolor_tile()undcolor_bar()ausformattablefügst du Farbe hinzu und erhältst eine ansprechend formatierte Tabelle. Drucke die Tabelle und vergleiche die Lexika.Die Tabelle vermittelt Größe und Struktur der einzelnen Lexika.
Match: Was überschneidet sich?
Um zu sehen, welches Lexikon besser zu Lyrics passt, betrachtest du den Match-Anteil der Wörter, die in Lexikon und Lyrics vorkommen. Zur Erinnerung:
prince_tidyenthält 76.116 Wörter und 7.851 unterschiedliche Wörter.Wie viele davon sind in den Lexika enthalten?
Führe ein
inner_join()zwischenprince_tidyundnew_sentimentsaus und gruppiere nach Lexikon. Das NRC-Lexikon hat 10 Kategorien, und ein Wort kann in mehreren vorkommen (etwa negativ und traurig). Nutze dahern_distinct()insummarise(), um die Anzahl unterschiedlicher Wörter pro Lexikon zu erhalten.NRC enthält mehr unterschiedliche Wörter aus den Lyrics als AFINN oder Bing. Beachte, dass die Summe der Match-Ratios gering ist. Kein Lexikon deckt alle Wörter ab — und sollte es auch nicht. Viele Wörter sind neutral und haben kein Sentiment. 2000 etwa ist normalerweise neutral und daher nicht im Lexikon. Dennoch war das Jahr mit Ängsten belegt (Y2K), was im Song mitschwingen kann — typische Lexika erfassen das nicht.
Warum fehlen Wörter in Lexika?
Glaub’s nicht nur aufs Wort
Schau dir ein paar spezifische Wörter aus Prince’ Lyrics an, die das Sentiment beeinflussen könnten. Stehen sie in allen Lexika?
Controversy und dark erscheinen in NRC und Bing, gangster nur in Bing. Race fehlt komplett — dabei ist es zentral in Prince’ Lyrics. Lässt es sich klar einem Sentiment zuordnen? AFINN ist deutlich kleiner und enthält nur eines dieser Wörter.
Wortformen
Jetzt wird’s kniffliger. Sexualität ist ein häufiges Thema bei Prince. Wie beeinflussen unterschiedliche Wortformen die lexikonbasierte Sentimentanalyse? Hier alle Verwendungen des Wortstamms sex in den Lyrics. Vergleiche mit Bing und NRC und sieh dir die Matches an.
Auffällig: sexy kommt häufig vor, fehlt aber in dieser Form im NRC. sex findet NRC, Bing hingegen nicht. Würde es helfen, Stämme oder Wurzeln zu betrachten? Dein Text kann Vergangenheitsformen, Plurale oder Adverbien eines Wortstamms enthalten, die in keinem Lexikon stehen. Wie gehst du damit um?
Mehr Datenvorbereitung?
Eventuell brauchst du weitere Vorbereitungsschritte. Drei Techniken, die du in Betracht ziehen solltest:
Fortgeschritten ist der Einsatz von Synonymen (semantisch ähnlichen Wörtern) und Hypernymen (übergeordneten Begriffen). Diese werden häufiger verwendet als das Wort im Song und tauchen im Lexikon auf — der Match-Anteil steigt. Für dieses Tutorial sprengt das den Rahmen, aber es lohnt sich!
Challenge: Recherchiere, wie Lexika entstehen. Gibt es eines, das sich besser für Lyrics eignet? Wenn dich das Thema packt: Was bräuchtest du, um ein eigenes Lexikon zu bauen? Und worin unterscheidet sich klassifikatorbasierte von lexikonbasierter Sentimentanalyse?
Detaillierte Analyse
Mit dem Grundlagenwissen zu Datensatz und Lexika kannst du beide für Analysen zusammenführen. Die Schritte:
Sentiment-Datensätze erstellen
Erzeuge für jedes Lexikon einen Prince-Sentiment-Datensatz, indem du per
inner_join()gegenget_sentiments()joinst. Übergib jeweils den Lexikon-Namen. Für diese Übung: Bing für binäre und NRC für kategoriale Sentiments. Da Wörter im NRC in mehreren Kategorien stehen können (z. B. Negative/Fear oder Positive/Joy), erzeugst du zusätzlich einen Subset ohne positive und negative Kategorien.In the Mood: Gesamtsentiment
Für die Detailanalyse willst du verschiedene Ebenen betrachten: alle Songs, Chart-Level, Jahrzehnt und Wort. Starte mit der NRC-Sentimentanalyse des gesamten Datensatzes.
(Nur zum Spaß: Mit den Paketen
memeryundmagicklassen sich Bilder (Memes) in Plots einbetten.)Bei Prince’ Lyrics überwiegt im NRC offenbar das Positive. Sind alle Wörter mit disgust oder anger zugleich in der Kategorie negative? Ein Blick könnte sich lohnen.
Jetzt das Bing-Gesamtsentiment. Von den 1.185 unterschiedlichen Wörtern aus Prince’ Lyrics, die im Bing-Lexikon auftauchen: Wie viele sind positiv, wie viele negativ?
Im Bing-Lexikon wirkt es fast ausgeglichen zwischen positiv und negativ. Hebelt sich Gesamtsentiment aus, wenn der Datensatz zu groß ist? Schwer zu sagen — betrachte Teilmengen und vergleiche.
In der Akustik gibt es Phasenauslöschung: Treffen zwei identische Wellen phaseninvertiert aufeinander, löschen sie sich aus — totale Stille auf dieser Frequenz! Was bedeutet das für Sentimentanalysen großer Datensätze? Denk mal darüber nach.
Lauter drehen: Chart-Level
Verfeinere die Analyse auf Chart-Level mit dem Bing-Lexikon. Erstelle einen Plot des polaren Sentiments je Chart-Level. Trenne Sentiments via
spread()in Spalten und berechne mitmutate()die Polarity (positive - negative) sowie percent_positive ($positive / total * 100$). Für den Polarity-Plot füge pergeom_hline()eineyinterceptein. Platziere beide Plots nebeneinander mitgrid.arrange().Ergibt sich hier, dass gechartete Songs tendenziell positiver sind? Falls ja: Was sagt das darüber aus, was das Publikum hören will? Darf man das so annehmen? Relativ zum Gesamtsentiment wirken gechartete Songs leicht positiver. Interessant — zumal das Bing-Lexikon insgesamt mehr negative als positive Wörter enthält.
Schmelzende Pole: So Blue
Wenn du polares Sentiment betrachtest, willst du wissen, ob es sich über die Zeit wettert — äh, ändert (Geek-Humor). Nutze
geom_smooth()mit Methodeloessfür eine weiche Kurve sowiegeom_smooth(method = "lm")für eine lineare.Einige Extreme wurden im zweiten Plot angepasst, aber der Gesamtrend des polaren Sentiments ist in beiden Fällen negativ.
Stimmungsring
Mit
chordDiagram()untersuchen wir die Beziehungen zwischen NRC-Sentiments und Jahrzehnten. Die Sentimentkategorien erscheinen auf der oberen, die Jahrzehnte auf der unteren Ringhälfte.Die Grafik zeigt Wortanzahlen pro NRC-Kategorie und Jahrzehnt. Auf kleiner Fläche viel Information — Beziehungen zwischen Kategorien und Zeit werden sichtbar. Die Diagramme sind extrem anpassbar — von simpel bis tief informativ.
Sentiment in Echtzeit
Nach all den DataCamp-Kursen juckt es dich sicher, Skills in der Praxis anzuwenden. Tun wir das, indem wir unsere Analyse von Prince’ Lyrics über Zeit mit realen Ereignissen verknüpfen. Bitte bleib kritisch: Eine vereinfachte Analyse trifft auf ein komplexes Thema.
Ich habe eine Liste von Lebensereignissen von Prince zusammengestellt (u. a. Rolling Stone, Biography.com). Ich habe öffentlich bekannte Jahre ausgewählt, die in unserem Datensatz mit Release-Daten abgedeckt sind. Lies nun
princeEvents.csvein. Erzeuge mitprince_bingundspread()einen Polaritätswert pro Jahr. Joine auf das Data Frameeventsund erstelle ein Feld für das Sentiment, damit du die Balken einfärben kannst. Bei langen Textlabels empfiehlt sichcoord_flip().Tipp:
princeEvents.csvfindest du hier.Der Vergleich von Ereignissen und Sentiment ist faszinierend. Subjektiv wirken sie sehr korreliert. Klar, ich habe den Ereignisdatensatz selbst erstellt und könnte ihn (unbewusst) an die Lyrics angepasst haben. Ich habe jedoch pro Jahr mehrere Quellen genutzt und mich auf die meistgenannten Fakten gestützt. Die Ergebnisse motivieren, auf der Wortebene tiefer zu graben — worüber sang Prince konkret?
The Black Album: 1994–1996
1994 veröffentlichte Prince The Black Album — ein Versuch, sein afroamerikanisches Publikum zurückzugewinnen. Da 1994 und die beiden Folgejahre in der Analyse herausstechen, betrachtest du die Top-Wörter, die im Zeitraum 1994–1996 im NRC-Lexikon matchen. Nutze
geom_label_repel()ausggrepelzusammen mitgeom_point(). Der Plot ist etwas knifflig — probiere ruhig an den Parametern.Prince sagte 1994 im Rolling Stone:
Er trat mit dem Wort „slave“ deutlich sichtbar im Gesicht auf — offener Protest gegen sein damaliges Label. Zudem änderte er seinen Namen in ein Symbol. Wie gut passen die oben gezeigten Wörter zu diesen Ereignissen? Siehst du „slave“, „emancipation“, „black“ und „change“?
(Das gilt nicht nur für Prince. Du kannst das mit jedem Text tun! Beispiel: Welche trust-Wörter nutzte Präsident Trump in seinem ersten Amtsjahr am häufigsten?)
Dieses Mal ist es persönlich: 1998
Der Plot Sentiment by Events zeigte: Zwischen 1996 und 1998 heiratete Prince, verlor zwei Kinder und soll auf der Bühne 9/11 angedeutet haben. (Suche auf YouTube nach seinem 1998er Konzert in den Niederlanden!) Nutze die gleichen Schritte und betrachte die Top-10-Wörter je NRC-Kategorie für 1998.
Angesichts persönlicher Verluste und der Bedrohung durch Terror sind Wörter wie „suicide“, „waste“, „mad“, „hurt“ — und viele hier nicht gezeigte — sehr treffend. Sie sind stark und geben echten Einblick in die Stimmung dieser Zeit.
Auf dem Radar: Radar-Charts
Ein weiterer Vergleich über Kategorien gelingt mit Radar- bzw. Spider-Charts. Erstellen kannst du sie mit dem Paket
radarchart. Ideal, um ähnliche Ausprägungen oder Ausreißer je Variable zu erkennen.Wir brechen die Analyse auf drei Ebenen herunter: Jahr, Chart und Jahrzehnt. (Aus Platzgründen zeige ich nur den Code für spezifische Jahre.)
prince_nrc_subohne positive und negative, damit die übrigen Kategorien besser sichtbar sind. Berechne zunächst die Wortanzahl je Sentiment und Jahr sowie die Gesamtanzahl und leite den Prozentwert ab ($Sentimentwörter pro Jahr / Gesamt pro Jahr * 100$).select().spread()Jahr und Prozentwerte in Spalten, sodass je Sentiment eine Zeile und je Jahr eine Spalte entsteht. Erzeuge dann mitchartJSRadar()ein interaktives Widget. Du kannst Labels im Mouseover anzeigen lassen. (FYI: Manchmal kapptradarchartdas J und Y von „joy“ — sieht dann wie „iov“ aus.)Du musst etwas scrollen, um die Plots zu vergleichen — dafür bleiben sie gut lesbar. Bemerkenswert: Bei kleineren Datensätzen wie
yearerkennst du Varianzen pro Sentiment deutlicher. Zudem fällt auf, dass im Prozentvergleich „joy“ überall am höchsten liegt (besonders zu Beginn von Prince’ Karriere 1978).Sign O' the Times
Spotify nutzt Machine Learning für Empfehlungen an 140 Millionen aktive Nutzer:innen. Lyrik-Inhalte fließen bisher nicht ein. Stell dir vor, sie täten es … Werfen wir einen tieferen Blick in die Stimmung einzelner Songs.
1987 schrieb Prince „Sign O' the Times“. Ein Billboard-Artikel von Kenneth Partridge sagt:
Wie würde eine Maschine die Stimmung des Songs interpretieren? Hoch emotional oder eher informativ? Spiegelt er Prince als Person oder die Gesellschaft? Plotte die NRC-Kategorien mit
ggplot2.Subjektiv, aber die Ergebnisse scheinen Partridge zu bestätigen: Prince „bleibt cool und distanziert“. Dafür spricht, dass anticipation-Wörter häufiger sind als emotionale Kategorien wie sadness, fear und anger. Dieser Artikel ordnet die drei als Emotionen ein, anticipation hingegen als Sentiment. Der Billboard-Autor bezeichnet den Song als „Status-Update, fast ohne Emotion“. Die Maschine scheint zuzustimmen. Siehst du das auch so? Sentimentanalyse ist nicht rein technisch — sie gilt als Schnittstelle, wo KI auf Psychologie trifft.
Mit
ggplot2als leicht veränderter Darstellung betrachten wir die Wörter je Kategorie.Kannst du die Wörter den Themen aus dem Billboard-Artikel zuordnen? Challenger-Katastrophe → „rocket“, AIDS → „disease“, Drogen → „crack“, Naturkatastrophen → „hurricane“, Gangs → „gang“ usw.
Fast so, als hätte Partridge vorher R-Code geschrieben und diesen Plot erzeugt!
Weitere Songs
Betrachte Sentimentkategorien für einige weitere Songs mit markanten Titeln und prüfe, ob es Korrelationen gibt.
NRC zeigt hohe anticipation und fear für einen Song über the future sowie zusätzlich hohe trust bei controversy. Ausgeprägt traurige Songs scheinen ihren Titeln zu entsprechen.
Bigrams pro Jahrzehnt
Bisher hast du Unigramme (einzelne Wörter) betrachtet. Aber wenn „love“ häufig ist: Was steht davor? Oder danach? Einzelwörter ohne Kontext können in die Irre führen. Zeit für Bigramme, also Wortpaare.
Praktisch:
tidytextkann auch Wortpaare entpacken. Rufeunnest_tokens()mittoken = ngramsauf. Da wir Bigramme betrachten, setzen = 2. Speichere die Ergebnisse inprince_bigrams.Mit
tidyrkannst du Bigramme mittelsseparate()in einzelne Wörter zerlegen. Um Stopp- und unerwünschte Wörter zu entfernen, trennst du das Bigramm, filterst Unerwünschtes heraus und setzt die Paare perunite()wieder zusammen. So visualisierst du die häufigsten Bigramme pro Jahrzehnt. (Teil Eins erklärtslice()undrow_number().)Über Bigramme erkennst du einen Wechsel gängiger Phrasen: von Sex, Tanz und Romantik hin zu Religion und (Rainbow) Children. „Rainbow Baby“ nennen Eltern manchmal ein Kind, das nach einer Fehlgeburt geboren wird. Interessanterweise fand ich keine ernsthafte Rezension zu Prince’ „Rainbow Children“-Album, die das erwähnt.
Sentiment mit Bigrammen
Wie beeinflussen Bigramme das Sentiment? Nutze das AFINN-Lexikon, um Sentiment bei Wortpaaren zu analysieren — speziell wenn Sentiment-Wörter durch „not“ oder andere Negationen relativiert werden.
In der ersten Zeile erhält care ein false positive, weil das „not“ bei Unigramm-Analyse ignoriert wird. Heben sich falsche Positive und falsche Negative gegenseitig auf?
Auch das bleibt eine offene Frage — ein gutes Thema für weitere Untersuchungen. Tipp: Mit
token = "trigrams"undn = 3inunnest_tokens()kannst du drei aufeinanderfolgende Wörter analysieren!Weitere Negationswörter solltest du ebenfalls betrachten. Erstelle nun ein Netzwerkdiagramm mit
ggraphundigraph. Ordne die Wörter als verbundene Knoten an, mit den Negationswörtern im Zentrum. Erzeuge das erste Objekt aus dem tidy Datensatz mitgraph_from_data_frame()und plottet mitggraph().geom_edge_density()hebt wichtige Knoten hervor. Ein ähnliches Beispiel findest du im Buch von Julia Silge und David Robinson zu Tidy Text Mining.Hier siehst du Wortpaare rund um Negationswörter. Wenn in einer Unigramm-Analyse „alone“ negativ wirkt, kehrt das Bigramm „not alone“ den Effekt um. Manche Wörter verbinden mehrere Knoten — gut sichtbar: „never hurt“ und „not hurt“.
Paarweise Vergleiche
Nachdem du n-Gramme gesehen hast, wirf einen Blick auf Korrelationen zwischen Wörtern. Welche Wörter sind am stärksten korreliert? Nutze
pairwise_count()auswidyrfür Co-Occurrence-Counts — also: Wie oft treten Wortpaare gemeinsam in einem Song auf?widyrarbeitet mit tidy Datensätzen, weitet sie vorübergehend und gibt sie tidy zurück.Zur Vereinfachung habe ich vier markante Wörter aus Prince’ Lyrics gewählt.
Im Vergleich dazu die paarweise Korrelation — wie oft Wörter gemeinsam auftreten relativ zu ihrem Einzelauftreten.
pairwise_cor()bestimmt Korrelationen basierend auf Co-Occurrence in denselben Songs.Schon diese vier Wörter liefern spannende Einblicke! Es zeichnen sich Themen ab — perfekte Überleitung zum nächsten Tutorial über Topic Modeling.
Fazit
In diesem Tutorial hast du ein tidy Data Set erstellt und Basisinformationen analysiert: lexikalische Diversität, Songanzahl pro Veröffentlichungsjahr und den Zusammenhang zwischen Jahrzehnt und Charterfolg. Du hast Sentiment-Lexika untersucht und ihre Passung zu den Lyrics bewertet. Danach folgten Sentimentanalysen über alle Songs, über die Zeit, auf Song-Ebene und mit Blick auf Bigramme — visualisiert mit vielfältigen, aussagekräftigen Plots.
Ist es möglich, ein Programm zu schreiben, das die Stimmung in Lyrics erkennt? Ja! Wie verlässlich sind die Ergebnisse? Das hängt von vielen Faktoren ab: Datenvorbereitung, Lexikonwahl, Methode, Datenqualität usw. Der Abgleich mit realen Ereignissen — persönlich und gesellschaftlich — kann die Stimmung eines Liedtexts erhellen. Princes polares Sentiment scheint über die Zeit leicht zu sinken, dennoch sticht joy insgesamt hervor. Gechartete Songs wirken positiver als ungechartete. Die Behauptungen rund um 9/11 und seinen Tod scheinen auf unheimliche Weise zu seinen Worten zu passen.
Aber Lyrics sind komplex, und zu viele Annahmen sind gefährlich. Wenn du die hier gewonnenen Erkenntnisse in das nächste Tutorial, Teil Zwei-B zu Topic Modeling, mitnimmst, bist du auf dem besten Weg, Themen und Motive herauszuarbeiten. Wie viel NLP ist bei Lyrics ohne Interpunktion oder Satzstruktur möglich? Wovon hat Prince wirklich gesungen? Jetzt, da du die Stimmung kennst, bist du bereit, potenzielle Themen zu untersuchen. Und schließlich: Lassen sich Jahrzehnt oder Chart-Level eines Songs vorhersagen? Finde es in den nächsten Tutorials heraus!
Ich hoffe, dir hat die Reise bisher gefallen. In Teil Zwei-B feiern wir „wie 1999“!