Leer hoe je opgeruimde sentimentanalyse in R uitvoert op nummers van Prince, sentiment in de tijd, sentimentscores per nummer, de impact van bigrams, en nog veel meer!
Maak een sentimentele reis door het leven en de tijden van Prince, The Artist, in deel Twee-A van een driedelige tutorialserie die sentimentanalyse met R gebruikt om inzicht te krijgen in The Artist’s carrière en maatschappelijke invloed. De drie tutorials behandelen het volgende:
Wist je dat Prince op het podium 9/11 voorspelde, drie jaar voordat het gebeurde? Mogelijk voorspelde hij ook zijn eigen dood. Zou dat kunnen? Het zou interessant zijn om zijn songteksten te onderzoeken en zelf een weloverwogen oordeel te vellen. Op 21 april 1985 nam hij "Sometimes It Snows In April" op. Hij stierf exact 31 jaar later op 21 april 2016. Kijk naar zijn woordkeuze in de sentimentanalyse van dit nummer:
Sentimentanalyse van "Sometimes It Snows In April"
Dit geeft een nieuwe betekenis aan predictive analytics!
Structuur
Elk codefragment wordt gevolgd door een inzicht dat meestal subjectief van aard is. Voel je vrij om van sectie naar sectie te gaan en je eigen aannames te maken als je dat wilt! Ik raad je aan de volgende inleiding te lezen, maar als je meteen naar de code wilt, klik dan hier. Begrijp je het concept achter lexicons al, neem dan de snelle route rechtstreeks naar de sectie met gedetailleerde analyse.
Introductie
Drietallen: samenvatting in drie delen
Ik heb net twee kittens en een puppy gered, dus ik denk nu in drieën. Bovendien is de regel van drie een schrijfprincipe dat suggereert dat dingen die in drietallen komen inherent grappiger, bevredigender of effectiever zijn dan andere aantallen of dingen — daarom heb ik dit in drie delen opgedeeld... In Deel één maakte je kennis met textmining en verkennende analyse met een dataset van honderden songteksten van de legendarische artiest Prince. In Deel Twee focus je op sentimentanalyse (Twee-A) en topic modeling (Twee-B) met NLP. Deel Drie sluit af met aanvullende voorspellende analysetaken met machinelearningtechnieken rond vragen zoals of het mogelijk is te bepalen in welk decennium een nummer is uitgebracht of — interessanter nog — te voorspellen of een nummer de Billboard-hitlijsten haalt op basis van de songtekst. Doorheen deze tutorials gebruik je verschillende machinelearningalgoritmen — elk gemarkeerd in rood in de grafiek hieronder.
Aangepast van de grafiek die hier wordt getoond. Let op dat de verschillende aspecten van modellering en machinelearningtechnieken niet per se in één hokje passen zoals hierboven. Dit is enkel om je een idee te geven van wat je kunt verwachten.
Uit elkaar gaan is moeilijk
Ja, ik had me vastgelegd op slechts drie delen, maar ik heb besloten Deel Twee op te splitsen in Twee-A (sentimentanalyse) en Twee-B (topic modeling — volgt binnenkort), wat resulteert in vier afzonderlijke tutorials. Er zijn veel goede artikelen en cursussen over het proces achter sentimentanalyse, dus het doel in Deel Twee-A is te focussen op het afleiden van inzichten uit de analyse — niet alleen het schrijven van code. Je gebruikt de songteksten van Prince als voorbeeld, maar je kunt de stappen toepassen op je eigen favoriete artiest. Je reis door de volledige tutorialserie moet hopelijk je verwondering prikkelen over de mogelijkheden van tekstuele (lyrische) analyse, terwijl je wordt ingewijd in Natural Language Processing (NLP) en machinelearningtechnieken.
Vereisten
Deel Twee-A van deze tutorialserie vereist een basisbegrip van tidy data — specifiek pakketten zoals dplyr voor datatransformatie, ggplot2 voor visualisaties, en de %>%pipe-operator (oorspronkelijk uit het magrittr-pakket). Je ziet dat verschillende stappen vaak gecombineerd worden met de %>%-operator om de code compact en herbruikbaar te houden.
Omdat deze tutorial ook een casestudy is, zit hij vol met visualisaties die verder gaan dan de gebruikelijke grafieken en diagrammen. Daarom is nog een vereiste dat je nieuwe pakketten kunt installeren in je eigen omgeving. Om te focussen op het vastleggen van inzichten, heb ik code opgenomen die is ontworpen om hergebruikt te worden op je eigen dataset, maar voor een gedetailleerde uitleg is verder onderzoek van jouw kant nodig. Voor elk pakket worden ondersteunende links gegeven. Zo maak je kennis met nieuwe tools, terwijl je focust op hun toepassing.
Tip: lees eerst Deel één om een basis te leggen voor de tutorialserie en inzicht te krijgen in de dichte en diverse woordenschat van de songteksten van Prince.
Overzicht van sentimentanalyse
Methode: Sentimentanalyse is een vorm van textmining die erop gericht is de opinie en subjectiviteit van inhoud te bepalen. Toegepast op songteksten kunnen de resultaten niet alleen representatief zijn voor de houding van de artiest, maar ook wijdverspreide culturele invloeden onthullen. Er zijn verschillende methoden voor sentimentanalyse, waaronder het trainen op een bekende dataset, het opstellen van eigen classificatieregels en het gebruik van vooraf gedefinieerde lexicale woordenboeken (lexicons). In deze tutorial gebruik je de lexicongebaseerde aanpak, maar ik raad je aan de andere methoden en hun afwegingen ook te onderzoeken.
Niveaus: Net zoals er verschillende methoden zijn voor sentimentanalyse, zijn er ook verschillende niveaus van analyse op basis van de tekst. Deze niveaus zijn meestal document, zin en woord. Bij songteksten kan het document worden gedefinieerd als sentiment per decennium, jaar, chartniveau of nummer. Zinsniveau is bij songteksten vaak geen optie, omdat interpunctie rijm en patronen kan verstoren. Woordniveau-analyse onthult gedetailleerde informatie en kan dienen als basiskennis voor meer geavanceerde praktijken in topic modeling.
Bereid je vragen voor!
Elk data science-project moet een set vragen hebben om te onderzoeken. Houd de volgende vragen in gedachten terwijl je met deze tutorial aan de slag gaat: is het mogelijk een programma te schrijven dat de stemming in songteksten bepaalt? Zijn vooraf gedefinieerde lexicons voldoende? Hoeveel datapreparatie is nodig? Kun je je resultaten koppelen aan echte gebeurtenissen? Verandert sentiment in de tijd? Zijn hits positiever of negatiever dan nummers die niet in de hitlijsten staan? Welke woorden springen eruit in de songteksten in het jaar waarin gezegd wordt dat Prince 9/11 voorspelde? Voorspelde hij zijn eigen dood?
Tip: lyrische analyse is heel anders en meestal complexer dan toespraken of boeken, waardoor het lastig is om inzichten te vangen. Wees dus voorzichtig met alle aannames! In deze tutorial zal ik meer vragen dan antwoorden aandragen, dus wees bereid om buiten de rechthoekige parallellogram te denken!
Voorbereidend werk
Libraries en functies
Om te beginnen met het analyseren van de songteksten van Prince, laad je de onderstaande libraries. Dit lijkt misschien veel, maar de meeste zijn voor grafieken en diagrammen. Gezien het frequente gebruik van visuals is het prettig om een standaardkleurschema te definiëren voor consistentie. Ik heb een lijst gemaakt met specifieke ANSI-kleuren. De functie theme() uit ggplot2 maakt het mogelijk afzonderlijke grafieken aan te passen, dus maak je ook je eigen functie, theme_lyrics(), die de standaardinstellingen wijzigt. Het pakket knitr is een engine voor dynamische rapportgeneratie met R. Gebruik dit samen met kableExtra en formattable om aantrekkelijke tekttabellen met kleur te maken. Maak opnieuw je eigen functie, my_kable_styling(), om de output van deze libraries te standaardiseren. De andere pakketten noem ik zodra ze gebruikt worden.
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)}Mogelijk gemaakt door
Sushi-data
Zoals altijd begin je met het inlezen van de ruwe data in een data frame. In Deel één heb je de oorspronkelijke dataset opgeschoond, zoals het uitschrijven van samentrekkingen, het verwijderen van escape-sequenties en het omzetten van tekst naar kleine letters. Je hebt die schone dataset opgeslagen naar een CSV-bestand voor gebruik in deze tutorial. Gebruik read.csv() om prince_data te maken en houd in gedachten dat je songteksten tekenreeksen moeten zijn, dus zorg dat je stringsAsFactors = FALSE zet. Omdat je de rijnummers niet nodig hebt, zet je row.names = 1.
Let op dat je deze parameters kunt vermijden door dit in te lezen in een modern data frame, een tibble, met read_csv(), maar voor consistentie met Deel één wordt hier read.csv() gebruikt.
prince_data <- read.csv('prince_new.csv', stringsAsFactors =FALSE, row.names =1)Mogelijk gemaakt door
Verwijder woorden met minder dan drie tekens (vaak gebruikt voor fonetisch effect in muziek)
Splits de songteksten op in afzonderlijke woorden
Om je ruwe data in een opgeruimd formaat te zetten, gebruik je unnest_tokens() uit tidytext om prince_tidy te maken, dat de songteksten opbreekt in losse woorden met één woord per rij. Daarna kun je anti_join() en filter() uit dplyr gebruiken voor de resterende schoonmaakstappen. (Zie Deel één voor een gedetailleerde uitleg.)
#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 packageMogelijk gemaakt door
Je nieuwe dataset, prince_tidy, is nu in een getokeniseerd formaat met één woord per rij, samen met het nummer waaruit het komt. Je hebt nu een data frame van 76116 woorden en 10 kolommen.
Beschrijvende statistiek
Als je Deel één nog niet hebt gelezen, wil je misschien snel naar een paar samenvattende grafieken van de volledige dataset kijken. Dat doe je met creatieve grafieken uit de pakketten ggplot2, circlize en yarrr.
Opgeruimd en wel: woordenaantal per nummer
Een piraat zou shipshape zeggen wanneer alles netjes op orde is. Hier is dus een interessante weergave van de schone, opgeruimde data die de lexicale diversiteit toont, of anders gezegd: de woordenschat van de songteksten in de tijd. Een pirate plot is een geavanceerde methode om een continue afhankelijke variabele, zoals het aantal unieke woorden, uit te zetten als functie van een categorische onafhankelijke variabele, zoals decennium. Dit combineert ruwe datapunten, beschrijvende en inferentiële statistiek in één effectieve plot. Bekijk deze mooie blog voor meer details over pirateplot() uit het pakket yarrr.
Maak het data frame word_summary dat het aantal onderscheidende woorden per nummer berekent. Hoe diverser de songtekst, hoe groter de woordenschat. Zo naar de data kijken bereidt je voor op analyse op woordniveau. Zet het decenniumveld op de waarde "NONE" voor nummers zonder releasedatum en herlabel die velden met schonere labels met select().
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 sizeMogelijk gemaakt door
Elke gekleurde cirkel in deze pirate plot staat voor een nummer. Het dichte rode gebied met de waarde "NONE" laat zien dat een groot aantal nummers in de dataset geen releasedatum heeft. Er is een lichte stijgende trend in het unieke aantal woorden per nummer in de vroege decennia: de vaste horizontale lijn toont het gemiddelde aantal woorden voor dat decennium. Dit is belangrijk om te weten als je het sentiment in de tijd gaat analyseren. De woorden worden door de loop van Prince’ carrière steeds veelzeggender.
Ik daag je uit om pirate plots verder te verkennen, want je hebt met deze grafiek nog maar het oppervlak aangeraakt!
Het hele jaar door: aantal nummers per jaar
Circulaire grafieken zijn een unieke manier om complexe (of simpele!) relaties tussen meerdere categorieën te visualiseren. (En lp’s waren rond!). De onderstaande grafiek is simpelweg een circulaire staafgrafiek met coord_polar() uit ggplot2 die het relatieve aantal nummers per jaar toont. Het is veel code voor zo’n eenvoudige grafiek, maar absoluut de moeite. Een soortgelijk voorbeeld vind je uitgebreider in de R Graph Gallery hier. Net als bij pirate plots is dit slechts een introductie tot wat je kunt doen met circulaire plots.
Aantal nummers per jaar
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)))Mogelijk gemaakt door
Zie je de opening helemaal bovenaan? Dit wijst op de honderden nummers zonder releasedata die je in de pirate plot zag. Met zo’n groot aantal niet-uitgebrachte nummers zou de grafiek onbruikbaar worden als je ze zou meenemen. De ontbrekende jaren geven de jaren aan waarin geen nummers zijn uitgebracht. De meest productieve jaren waren 1996 en 1998.
Wil je weten waarom? Lees verder!
Akkoorden: nummers in de hitlijsten per decennium
De volgende grafiek laat de relatie zien tussen het decennium waarin een nummer werd uitgebracht en of het de Billboard-hitlijsten haalde. Een chordDiagram() gebruiken voor muzikale analyse leek gewoon passend! Deze grafische tool komt uit het prachtige circlize-pakket van Zuguang Gu. De grafiek is opgesplitst in twee categorieën: chartstatus (boven) en decennium (onder). De twee categorieën zijn gescheiden door brede openingen, met kleinere openingen tussen de waarden. De algemene opmerkingen in de onderstaande code kun je hier aanvullen met meer detail.
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")Mogelijk gemaakt door
De bovenstaande cirkelgrafiek lijkt op het eerste gezicht complex, maar laat mooi de aantallen nummers per decennium per chartniveau zien. Je ziet dat Prince zijn carrière in de jaren zeventig begon met slechts enkele releases, waarvan sommige de hitlijsten haalden. Vergelijk je de jaren tachtig met de jaren negentig, dan zie je dat er in de jaren negentig meer nummers zijn uitgebracht, maar dat in de jaren tachtig meer nummers de hitlijsten haalden. Er waren slechts enkele commercieel succesvolle nummers in de jaren 2000 en in de jaren 2010 waren er geen hits.
Lexicons en lyrics
In deze sectie ga je:
De sentimentlexicons en hun woordenaantallen verkennen
Bepalen hoeveel woorden uit de songteksten in de lexicons voorkomen
Naar specifieke woorden en woordvormen kijken
Extra datapreparatie overwegen
Verken sentimentlexicons
Het pakket tidytext bevat een dataset genaamd sentiments met verschillende afzonderlijke lexicons. Deze lexicons zijn woordenboeken met woorden waaraan een sentimentcategorie of -waarde is toegekend. tidytext biedt drie lexicons voor algemeen gebruik:
AFINN: kent woorden een score toe tussen -5 en 5, waarbij negatieve scores negatief sentiment aangeven en positieve scores positief sentiment
Bing: deelt woorden in in positieve en negatieve categorieën
NRC: deelt woorden in in een of meer van de volgende tien categorieën: positive, negative, anger, anticipation, disgust, fear, joy, sadness, surprise en trust
Om de lexicons te bekijken, maak je een data frame new_sentiments. Filter een financieel lexicon eruit, maak voor het AFINN-lexicon een binaire (ook wel polair genoemde) sentimentkolom door de numerieke score om te zetten naar positive of negative, en voeg een veld toe met het aantal verschillende woorden per lexicon.
new_sentiments heeft één kolom met de verschillende sentimentcategorieën. Voor een beter beeld van de woordenaantallen per lexicon, per categorie, gebruik je spread() uit tidyr om die categorieën te pivoteren naar afzonderlijke velden. Profiteer van de pakketten knitr en kableExtra in de eerder gemaakte functie my_kable_styling(). Voeg kleur toe aan je tabel met color_tile() en color_bar() uit formattable om een mooi geformatteerde tabel te maken. Print je tabel en bekijk de verschillen tussen de lexicons.
De bovenstaande tabel geeft je een idee van de omvang en structuur van elk lexicon.
Match dot common
Om te bepalen welk lexicon het meest toepasbaar is op de songteksten, wil je kijken naar de matchratio van woorden die zowel in het lexicon als in de lyrics voorkomen. Ter herinnering: er zijn 76116 totale woorden en 7851 verschillende woorden in prince_tidy.
Hoeveel van die woorden staan er daadwerkelijk in de lexicons?
Gebruik een inner_join() tussen prince_tidy en new_sentiments en groepeer dan per lexicon. Het NRC-lexicon heeft 10 verschillende categorieën en een woord kan in meer dan één categorie voorkomen: woorden kunnen bijvoorbeeld negatief en verdrietig zijn. Dat betekent dat je n_distinct() in summarise() wilt gebruiken om het aantal verschillende woorden per lexicon te krijgen.
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")Mogelijk gemaakt door
Songteksten gevonden 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
Het NRC-lexicon bevat meer van de verschillende woorden uit de songteksten dan AFINN of Bing. Merk op dat de som van de matchratio’s laag is. Geen enkel lexicon kan alle woorden bevatten, en dat hoeft ook niet. Veel woorden zijn neutraal en hebben geen bijbehorend sentiment. Bijvoorbeeld, 2000 is doorgaans een neutraal woord en staat daarom niet in de lexicons. Maar als je je herinnert, voorspelden mensen dat vliegtuigen uit de lucht zouden vallen en computers zouden stoppen met werken in dat jaar. Er is dus een bijbehorende angst in de songtekst die niet wordt vastgelegd in sentimentanalyse met standaardlexicons.
Hier zijn enkele redenen waarom een woord niet in een lexicon voorkomt:
Niet elk woord draagt sentiment.
De lexicons zijn gemaakt voor andere soorten tekst, dus niet voor songteksten.
De feitelijke vorm van het woord komt mogelijk niet voor. Bijvoorbeeld, strong komt misschien voor, maar strongly niet. Er kan meer schoonmaak nodig zijn in de data! (Hier kom ik later kort op terug.)
Geloof me niet op m’n woord
Kijk naar enkele specifieke woorden uit de songteksten van Prince die waarschijnlijk impact hebben op sentiment. Staan ze in alle lexicons?
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")Mogelijk gemaakt door
Specifieke woorden
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 en dark komen voor in NRC en Bing, maar gangster alleen in Bing. Race komt helemaal niet voor en is een cruciaal onderwerp in Prince’ songteksten. Maar is het makkelijk te koppelen aan een sentiment? Let op dat AFINN veel kleiner is en slechts één van deze woorden bevat.
Woordvormen
Nu een ingewikkelder voorbeeld. Seksualiteit is een terugkerend thema in Prince’ muziek. Hoe wordt sentimentanalyse op basis van vooraf gedefinieerde lexicons beïnvloed door verschillende vormen van een woord? Hier zijn bijvoorbeeld alle verwijzingen naar het stamwoord sex in de songteksten. Vergelijk deze met Bing en NRC en kijk waar er matches zijn.
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")Mogelijk gemaakt door
Afhankelijkheid van woordvorm
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
Merk op dat Prince sexy vaak gebruikt, maar dat dit in deze vorm niet in NRC voorkomt. Het woord sex staat wel in NRC maar niet in Bing. Wat als je naar de stammen of wortels van woorden kijkt, helpt dat? Wat een dilemma! Je tekst kan een voltooid deelwoord, een meervoud of een bijwoord van een stamwoord bevatten, maar het komt mogelijk in geen enkel lexicon voor. Hoe ga je daarmee om?
Meer datapreparatie?
Het kan nodig zijn dat je nog een paar stappen in de datapreparatie uitvoert. Hier zijn drie technieken om te overwegen voordat je sentimentanalyse doet:
Stemming: verwijst doorgaans naar het verwijderen van suffixen van woorden om de gemeenschappelijke oorsprong te krijgen
Lemmatization: het reduceren van verbogen (of soms afgeleide) woorden tot hun stam, basis- of grondvorm
Woordvervanging: woorden vervangen door vaker gebruikte synoniemen
Een geavanceerd concept in sentimentanalyse is dat van synoniem- (semantisch vergelijkbare “peers”) en hyperoniemvervanging (een gemeenschappelijke “overkoepelende” term). Dit zijn woorden die vaker worden gebruikt dan het gerelateerde woord in de songtekst en die wél in een lexicon voorkomen, wat zo een hogere matchpercentage oplevert. Er is niet genoeg ruimte in deze tutorial om extra datapreparatie te behandelen, maar het is zeker iets om te overwegen!
Uitdaging: doe wat onderzoek naar lexicons en hoe ze worden gemaakt. Bestaat er al een dat beter geschikt is voor songteksten? Als je echt geïnteresseerd bent, overweeg dan wat er nodig is om je eigen lexicon te bouwen. Wat is het verschil tussen classifier-gebaseerde en lexicon-gebaseerde sentimentanalyse?
Gedetailleerde analyse
Nu je een basisbegrip hebt van de dataset en de lexicons, kun je die kennis toepassen door ze samen te voegen voor analyse. Dit zijn de stappen op hoofdlijnen:
Maak lexiconspecifieke datasets
Kijk naar polaire sentimenten over alle nummers
Onderzoek sentimentverandering in de tijd
Valideer je resultaten aan de hand van specifieke gebeurtenissen in Prince’ leven
Bestudeer sentimentscores op nummerniveau
Bekijk hoe woordparen het sentiment beïnvloeden
Maak sentimentdatasets
Begin met het maken van Prince-sentimentdatasets voor elk van de lexicons door een inner_join() uit te voeren op de functie get_sentiments(). Geef de naam van het lexicon bij elke aanroep door. Gebruik voor deze oefening Bing voor binaire en NRC voor categorische sentimenten. Omdat woorden in meerdere NRC-categorieën kunnen voorkomen, zoals Negative/Fear of Positive/Joy, maak je ook een subset zonder de categorieën positive en negative om later te gebruiken.
prince_bing <- prince_tidy %>%
inner_join(get_sentiments("bing"))Mogelijk gemaakt door
prince_nrc <- prince_tidy %>%
inner_join(get_sentiments("nrc"))Mogelijk gemaakt door
prince_nrc_sub <- prince_tidy %>%
inner_join(get_sentiments("nrc"))%>%
filter(!sentiment %in% c("positive","negative"))Mogelijk gemaakt door
In the mood: overall sentiment
In de gedetailleerde analyse van de songteksten wil je de verschillende niveaus van tekst bekijken, zoals alle nummers, chartniveau, decennium en woordniveau. Begin met het plotten van de NRC-sentimentanalyse van de hele dataset.
(Voor de leuk heb ik de pakketten memery en magick gebruikt om afbeeldingen (memes) aan de grafieken toe te voegen.)
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)Mogelijk gemaakt door
Het lijkt erop dat NRC voor Prince’ songteksten sterk de positive kant op leunt. Maar staan alle woorden met een sentiment disgust of anger ook in de categorie negative? Het kan de moeite zijn om dat te checken.
Kijk nu naar het algemene sentiment volgens Bing. Van de 1185 verschillende woorden uit de songteksten van Prince die in het Bing-lexicon voorkomen: hoeveel zijn positief en hoeveel negatief?
bing_plot <- prince_bing %>%
group_by(sentiment)%>%
summarise(word_count = n())%>%
ungroup()%>%
mutate(sentiment = reorder(sentiment, word_count))%>%
ggplot(aes(sentiment, word_count, fill = sentiment))+
geom_col()+
guides(fill =FALSE)+
theme_lyrics()+
labs(x =NULL, y ="Word Count")+
scale_y_continuous(limits = c(0,8000))+
ggtitle("Prince Bing Sentiment")+
coord_flip()
img1 <-"prince_background1.jpg"
lab1 <-""
meme(img1, lab1,"meme_bing.jpg", inset = bing_plot)
x <- image_read("meme_bing.jpg")
plot(x)Mogelijk gemaakt door
Zou het zo kunnen zijn dat Bing ongeveer evenveel positive als negative sentiment laat zien in Prince’ muziek? Heft overall sentiment zichzelf op wanneer je naar een dataset kijkt die te groot is? Moeilijk te zeggen, maar bekijk het in stukken en kijk wat er gebeurt.
In de akoestiek bestaat fase-annulering waarbij de frequentie van twee instanties van dezelfde golf precies uit fase is en elkaar opheffen, bijvoorbeeld wanneer je een drum opneemt met twee microfoons en het geluid langer doet over een van de microfoons. Dit resulteert in totale stilte op die frequentie! Hoe zou dit kunnen gelden voor sentimentanalyse van grote datasets? Denk er eens over na.
Taalkundeprofessor: "In het Engels vormt een dubbele ontkenning een bevestiging. In het Russisch is een dubbele ontkenning nog steeds een ontkenning. Er is echter geen taal waarin een dubbele bevestiging een ontkenning kan vormen." Oneens zijnde student: "Ja hoor."
Harder zetten: chartniveau
Zet het volume van je analyse harder door het op te splitsen naar chartniveau met het Bing-lexicon. Maak een grafiek van het polaire sentiment per chartniveau. Gebruik spread() om de sentimenten in kolommen te splitsen en mutate() om een polarity-veld (positive - negative) en een veld percent_positive ($positive / total sentiment * 100$) te maken, voor een ander perspectief. Voeg voor de polariteitsgrafiek een yintercept toe met geom_hline(). Plot de grafieken naast elkaar met 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)Mogelijk gemaakt door
Zegt dit dat nummers die de hitlijsten haalden doorgaans positiever zijn dan negatiever? Zo ja, wat zegt dit over wat het publiek wil horen? Kun je zulke aannames wel maken? Kijkend naar het positieve sentiment relatief aan het totale sentiment, lijken de genoteerde nummers net iets positiever dan negatief. Dat is interessant, gezien het Bing-lexicon zelf meer negatieve dan positieve woorden heeft.
Poolsmelting: So Blue
Omdat je naar sentiment vanuit een polair perspectief kijkt, wil je misschien zien of het weer verandert in de tijd (nerdhumor). Gebruik dit keer geom_smooth() met de methode loess voor een gladdere curve en nog een geom_smooth() met methode = lm voor een lineaire trendlijn.
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)Mogelijk gemaakt door
Enkele extremen zijn in de tweede grafiek aangepast, maar de algemene trend van de polariteit in de tijd is in beide gevallen negatief.
Sfeerring
Je gebruikt opnieuw de kracht van de chordDiagram() om de relaties tussen NRC-sentimenten en decennia te bekijken. Merk op dat de sentimentcategorieën bovenin de ring staan en de decennia onderin.
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")Mogelijk gemaakt door
Dit toont de aantallen woorden per NRC-categorie per decennium. Het is veel om in één kleine grafiek te verwerken, maar het geeft enorm veel informatie over relaties tussen categorieën en tijd. Deze diagrammen zijn ontzettend aanpasbaar en kunnen zo eenvoudig of informatief zijn als je wilt.
Sentiment in real time
Gegeven al die heerlijke cursussen die je op DataCamp hebt gevolgd, sta je vast te trappelen om die skills toe te passen op échte situaties. Doe dat nu door je analyse van Prince’ songteksten te mappen op iets echts gedurende een bepaalde tijd. Wees echter sceptisch en voorzichtig wanneer je een vereenvoudigde analyse toepast op zo’n complex onderwerp.
Ik heb een lijst met levensgebeurtenissen van Prince gemaakt, verzameld uit populaire bronnen zoals Rolling Stone, Biography.com, enz. Ik heb publiekelijk bekende jaren gekozen die overeenkomen met nummers met releasedata in onze dataset. Lees die events nu in uit princeEvents.csv. Gebruik vervolgens prince_bing en spread() om een polariteitsscore per jaar te maken. Join met het data frame events en maak een sentimentveld zodat je kleuren kunt vullen in je staafdiagram. Gebruik zoals altijd coord_flip() bij grote tekstlabels.
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()Mogelijk gemaakt door
Ik vind het fascinerend om de gebeurtenissen met het sentiment te vergelijken. Hoewel subjectief, vond ik ze sterk gecorreleerd. Toegegeven, ik heb de eventdataset zelf samengesteld. Ik had hem gemakkelijk kunnen bevooroordelen om te matchen met de songteksten. Maar ik heb wel meerdere bronnen per jaar gebruikt om het meest gerapporteerde feit te bepalen. Deze resultaten zouden je moeten motiveren om nog dieper in een woordniveau-analyse te duiken om te zien waarover Prince zong.
The Black Album: 1994 - 1996
In 1994 bracht Prince The Black Album uit in een poging zijn Afro-Amerikaanse publiek terug te winnen. Gezien het feit dat 1994 en de twee daaropvolgende jaren opvallen in de analyse, kun je kijken naar de topwoorden die matchen met het NRC-lexicon voor deze periode met geom_label_repel() uit het pakket ggrepel samen met geom_point(). Dit is een lastige grafiek, dus ik raad aan met de configuratie van de onderstaande code te spelen.
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()Mogelijk gemaakt door
In 1994 zei Prince tegen Rolling Stone magazine,
"Als je een man belemmert om te dromen, wordt hij een slaaf. Daar was ik."
Hij verscheen dus in het openbaar met het woord "slave" prominent op zijn gezicht geschreven en verklaarde openlijk zijn anticorporatieve sentiment richting zijn platenlabel. Hij veranderde ook zijn naam in een symbool. Hoe goed matchen de bovenstaande woorden met deze gebeurtenissen? Zie je de woorden "slave", "emancipation", "black" en "change" in de grafiek?
(Onthoud, dit gaat niet alleen over Prince. Je kunt dit toepassen op elke tekst! Bijvoorbeeld: wat waren de meest trust-woorden die president Trump sprak in zijn eerste ambtsjaar?)
Dit keer persoonlijk: 1998
De grafiek Sentiment by Events hierboven gaf aan dat Prince tussen 1996 en 1998 trouwde, twee kinderen verloor en naar verluidt 9/11 op het podium voorspelde. (Zoek op YouTube naar zijn concert in 1998 in Nederland en luister zelf!) Gebruik dezelfde stappen als bij de vorige grafiek en bekijk de top 10 woorden in elke NRC-categorie in 1998.
Gezien het persoonlijke verlies en de dreigende terreurdreiging zijn woorden als "suicide", "waste", "mad", "hurt" en vele andere die niet in deze grafiek zijn opgenomen zeer kenmerkend voor de gebeurtenissen. Deze woorden zijn krachtig en geven echt inzicht in het sentiment van die periode.
Op de radar: radardiagrammen
Een andere goede manier om sentimenten over categorieën te vergelijken, is met een radardiagram, ook wel spinnenwebdiagram genoemd. Je kunt dit soort grafieken maken met het pakket radarchart. Ze zijn handig om te zien welke variabelen vergelijkbare waarden hebben of of er uitschieters zijn per variabele.
Je splitst deze analyse op in drie niveaus: jaar, chart en decennium. (Om ruimte te besparen laat ik alleen de code voor de specifieke jaren zien.)
Gebruik de dataset prince_nrc_sub die de sentimenten positive en negative niet bevat, zodat de andere duidelijker zichtbaar zijn. Bereken dit keer eerst het totale aantal woorden per sentiment per jaar, evenals het totale sentiment voor het hele jaar en verkrijg een percentage ($aantal sentimentwoorden per jaar / totaal per jaar * 100$).
Filter op de specifieke jaren 1978, 1994, 1995 en verwijder de onnodige velden met select().
Ten slotte moet je de jaar- en percentagewaarden (key/value-paren) spread()-en naar meerdere kolommen, zodat je één rij per sentiment hebt en een kolom per jaar. Gebruik dan chartJSRadar() om een interactieve HTML-widget te genereren. Je kunt een argument meegeven om datasetlabels in de mouse-over te tonen. (FYI, soms worden de J en Y van het woord "joy" door radarchart afgesneden en lijkt het op "iov".)
#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")Mogelijk gemaakt door
Je moet even scrollen om deze grafieken te vergelijken omdat ik ze op volledige grootte wilde laten. Interessant is dat je met een kleinere dataset zoals jaar de variatie in elk sentiment veel duidelijker ziet met dit type visualisatie. Je valt mogelijk ook op dat, in tegenstelling tot eerdere oefeningen, bij gebruik van percentages "joy" de hoogste score heeft op alle grafieken (vooral aan het begin van Prince’ carrière in 1978).
Sign O' the Times
Spotify gebruikt machine learning om aanbevelingen te doen voor zijn 140 miljoen actieve gebruikers. Het gebruikt songteksten nog niet. Stel je voor dat het dat wel zou doen... Neem nu een nog diepere kijk in de sfeer van specifieke nummers.
In 1987 schreef Prince een nummer genaamd "Sign O' the Times". Een Billboard-artikel van Kenneth Partridge stelde:
"Terwijl hij de onderwerpen AIDS, bendes, drugs, natuurrampen en zelfs de Challenger-explosie aanpakt, blijft Prince cool en afstandelijk. 'Sign O' the Times' is een statusupdate, geen oproep tot actie. Zijn oplossing is niet demonstreren op straat of je congreslid bellen."
Hoe zou een machine de sfeer van dit nummer interpreteren? Is het sterk emotioneel of simpelweg informatief? Vertegenwoordigt het Prince als persoon of de samenleving in het algemeen? Plot de NRC-categorieën met 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()Mogelijk gemaakt door
Hoewel opnieuw sterk subjectief, lijken je resultaten te bevestigen dat Prince "cool en afstandelijk" blijft, zoals Partridge stelt. Dat kun je afleiden uit de observatie dat anticipation-woorden vaker voorkomen dan emotionele categorieën als sadness, fear en anger. Hier is een artikel dat stelt dat deze drie categorieën emoties zijn, terwijl anticipation een sentiment is. De Billboard-auteur stelde ook dat het nummer simpelweg een "statusupdate, bijna zonder emotie" is. Het lijkt erop dat de machine het daarmee eens is. Interpreteer jij het ook zo? Ik weet dat dit een R-tutorial is, maar sentimentanalyse is niet puur technisch. Men zegt dat het de plek is waar AI psychologie ontmoet.
Gebruik ggplot2 om in een iets andere grafiek naar de woorden per categorie te kijken.
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()Mogelijk gemaakt door
Kun je deze woorden koppelen aan de onderwerpen in het Billboard-artikel? De connectie is vrij duidelijk: Challenger-ramp -> "rocket", AIDS -> "disease", drugs -> "crack", natuurrampen -> "hurricane", bendes -> "gang", enz.
Het is bijna alsof Partridge eerst wat R-code schreef en deze grafiek maakte voordat hij het artikel schreef!
Meer nummers
Bekijk de sentimentcategorieën voor nog een paar nummers met sprekende titels en kijk of ze gecorreleerd lijken.
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()Mogelijk gemaakt door
NRC-sentimenten tonen hoge anticipation en fear voor een nummer over the future, en hetzelfde plus hoge trust voor een nummer over controversy. De nummers die overwegend sad zijn, lijken eveneens bij hun titels te passen.
Bigrams per decennium
Tot nu toe heb je alleen gekeken naar unigrams of losse woorden. Maar als "love" een veelvoorkomend woord is, wat gaat eraan vooraf? Of volgt erop? Losse woorden uit hun context bekijken kan misleidend zijn. Tijd dus om naar bigrams of woordparen te kijken.
Handig genoeg biedt het pakket tidytext de mogelijkheid om paren van woorden te unnesten, evenals losse woorden. In dit geval roep je unnest_tokens() aan met het argument token op ngrams. Omdat je alleen naar bigrams (twee opeenvolgende woorden) kijkt, geef je n = 2 door. Gebruik prince_bigrams om de resultaten op te slaan.
Het pakket tidyr biedt de mogelijkheid om bigrams op te splitsen in individuele woorden met de functie separate(). Om de stopwoorden en ongewenste woorden te verwijderen, wil je de bigrams opsplitsen en filteren wat je niet wilt, en vervolgens met unite() de woordparen weer samenvoegen. Zo kun je eenvoudig de meest voorkomende bigrams per decennium visualiseren. (Zie Deel één voor een uitleg van slice() en 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())Mogelijk gemaakt door
## Joining, by = c("song", "year", "album", "peak", "us_pop", "us_rnb", "decade", "chart_level", "charted")Mogelijk gemaakt door
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()Mogelijk gemaakt door
Met bigrams zie je bijna hoe de veelgebruikte frasen verschuiven van sex, dance en romance naar religie en (rainbow) children. Voor het geval je het niet wist: de term "rainbow baby" wordt soms gebruikt door ouders die een volgend kind verwachten na het verlies van een baby door een miskraam. Opmerkelijk genoeg kon ik geen echte recensie van Prince’ album Rainbow Children vinden die hier melding van maakt.
Sentiment met bigrams
Hoe beïnvloeden bigrams het sentiment? Gebruik dit keer het AFINN-lexicon voor sentimentanalyse op woordparen en kijk hoe vaak sentimentgebonden woorden worden voorafgegaan door "not" of andere ontkennende woorden.
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()Mogelijk gemaakt door
Op de eerste regel van de grafiek krijgt care een vals positief sentiment omdat de "not" wordt genegeerd bij analyse van losse woorden. Heffen de vals-positieve bigrams de vals-negatieve bigrams op?
Weer een vraag die ik niet kan beantwoorden, maar wel een goed onderwerp om verder te verkennen. Tip: je kunt ook de parameters "trigrams" en "n = 3" meegeven aan unnest_tokens() om naar nog meer opeenvolgende woorden te kijken!
Er zijn ook andere ontkenningswoorden om te overwegen. Dit keer maak je een netwerkgrafiek met de pakketten ggraph en igraph. Je zet de woorden op in verbonden knopen met de ontkenningswoorden in het midden. Maak het eerste object uit de opgeruimde dataset met graph_from_data_frame() en gebruik vervolgens ggraph() om het te plotten. Je kunt de hoofdknopen markeren met een aanroep van geom_edge_density(). Meer details van een soortgelijk voorbeeld vind je in het boek van Julia Silge en David Robinson over Tidy Text Mining.
Hier zie je de woordparen die samenhangen met ontkenningswoorden. Als je analyse is gebaseerd op unigrams en "alone" komt negatief terug, zal het bigram "not alone", zoals je hierboven ziet, het omgekeerde effect hebben. Sommige woorden kruisen meerdere knopen, wat in een visual als deze goed te zien is: bijvoorbeeld "never hurt" en "not hurt".
Pairwise-vergelijkingen
Nu je naar n-grams hebt gekeken, bekijk je de correlatie tussen woorden. Welke woorden zijn het sterkst gecorreleerd? Gebruik de functie pairwise_count() uit het pakket widyr om co-occurrence-tellingen te identificeren. Dat wil zeggen: je telt hoe vaak elk woordpaar samen voorkomt binnen een nummer. Het pakket widyr neemt een opgeruimde dataset en verbreedt die tijdelijk voordat het deze weer terugbrengt naar een opgeruimde structuur voor visualisatie en verdere analyse.
Om het simpel te houden, heb ik vier interessante woorden uit Prince’ songteksten gekozen.
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()Mogelijk gemaakt door
Vergelijk dat met pairwise correlation. Dit verwijst naar hoe vaak woorden samen voorkomen relatief aan hoe vaak ze afzonderlijk voorkomen. Gebruik pairwise_cor() om de correlatie tussen woorden te bepalen op basis van hoe vaak ze in hetzelfde nummer voorkomen.
Ik vind dit fascinerende inzichten in de songteksten, alleen al op basis van deze vier woorden! Als je naar deze resultaten kijkt, beginnen er thema’s naar voren te komen. Dit vormt een mooie opstap naar de volgende tutorial over topic modeling!
Conclusie
In deze tutorial heb je een opgeruimde dataset gemaakt en basisinformatie geanalyseerd zoals de lexicale diversiteit en het aantal nummers per uitgiftejaar, en je bekeek de relatie tussen uitgifte-decennium en of een nummer de hitlijsten haalde. Je hebt vervolgens enkele sentimentlexicons verkend en gekeken hoe goed ze matchten met de songteksten. Daarna heb je sentimentanalyse uitgevoerd over alle nummers in de dataset, sentiment in de tijd, sentimentscores per nummer en de impact van bigrams. Dit deed je met een brede variatie aan interessante grafieken, die elk een ander perspectief geven.
Is het dus mogelijk een programma te schrijven om stemming in songteksten te bepalen? Jazeker! Hoe betrouwbaar zijn je resultaten? Dat hangt af van een breed scala aan criteria, zoals de hoeveelheid datapreparatie, de keuze van het lexicon, de analysemethode, de kwaliteit van de brondata, enzovoort. Het vergelijken met echte gebeurtenissen, zowel persoonlijk als maatschappelijk, kan de stemming van elke songtekst verduidelijken. Prince’ polaire sentiment leek in de tijd licht te dalen, maar overall lijkt joy eruit te springen. Nummers die de hitlijsten halen lijken positiever dan nummers die dat niet doen. De claims dat hij 9/11 en zijn eigen dood voorspelde, lijken griezelig goed te rijmen met zijn woorden.
Maar songteksten zijn complex en te veel aannames kunnen problemen veroorzaken. Als je meeneemt wat je over de songteksten hebt geleerd naar de volgende tutorial, Deel Twee-B over topic modeling, ben je goed op weg om thema’s en motieven uit te graven. Hoeveel NLP kun je toepassen op songteksten die zonder interpunctie of zinsstructuur verschijnen? Waar zong Prince nu echt over? Nu je de stemming begrijpt, ben je beter voorbereid om de mogelijke topics te onderzoeken. En tot slot: kun je machinelearningtechnieken toepassen om het decennium of het chartniveau van een nummer te voorspellen? Ga mee naar de volgende tutorials om het te ontdekken!
Ik hoop dat je de reis tot nu toe leuk vond. In Deel Twee-B gaan we feesten alsof het 1999 is!
Maak een sentimentele reis door het leven en de tijden van Prince, The Artist, in deel Twee-A van een driedelige tutorialserie die sentimentanalyse met R gebruikt om inzicht te krijgen in The Artist’s carrière en maatschappelijke invloed. De drie tutorials behandelen het volgende:
Wil je meer leren over sentimentanalyse? Bekijk dan zeker onze cursus Sentiment Analysis in R: The Tidy Way.
Wist je dat Prince op het podium 9/11 voorspelde, drie jaar voordat het gebeurde? Mogelijk voorspelde hij ook zijn eigen dood. Zou dat kunnen? Het zou interessant zijn om zijn songteksten te onderzoeken en zelf een weloverwogen oordeel te vellen. Op 21 april 1985 nam hij "Sometimes It Snows In April" op. Hij stierf exact 31 jaar later op 21 april 2016. Kijk naar zijn woordkeuze in de sentimentanalyse van dit nummer:
Dit geeft een nieuwe betekenis aan predictive analytics!
Structuur
Elk codefragment wordt gevolgd door een inzicht dat meestal subjectief van aard is. Voel je vrij om van sectie naar sectie te gaan en je eigen aannames te maken als je dat wilt! Ik raad je aan de volgende inleiding te lezen, maar als je meteen naar de code wilt, klik dan hier. Begrijp je het concept achter lexicons al, neem dan de snelle route rechtstreeks naar de sectie met gedetailleerde analyse.
Introductie
Drietallen: samenvatting in drie delen
Ik heb net twee kittens en een puppy gered, dus ik denk nu in drieën. Bovendien is de regel van drie een schrijfprincipe dat suggereert dat dingen die in drietallen komen inherent grappiger, bevredigender of effectiever zijn dan andere aantallen of dingen — daarom heb ik dit in drie delen opgedeeld... In Deel één maakte je kennis met textmining en verkennende analyse met een dataset van honderden songteksten van de legendarische artiest Prince. In Deel Twee focus je op sentimentanalyse (Twee-A) en topic modeling (Twee-B) met NLP. Deel Drie sluit af met aanvullende voorspellende analysetaken met machinelearningtechnieken rond vragen zoals of het mogelijk is te bepalen in welk decennium een nummer is uitgebracht of — interessanter nog — te voorspellen of een nummer de Billboard-hitlijsten haalt op basis van de songtekst. Doorheen deze tutorials gebruik je verschillende machinelearningalgoritmen — elk gemarkeerd in rood in de grafiek hieronder.
Uit elkaar gaan is moeilijk
Ja, ik had me vastgelegd op slechts drie delen, maar ik heb besloten Deel Twee op te splitsen in Twee-A (sentimentanalyse) en Twee-B (topic modeling — volgt binnenkort), wat resulteert in vier afzonderlijke tutorials. Er zijn veel goede artikelen en cursussen over het proces achter sentimentanalyse, dus het doel in Deel Twee-A is te focussen op het afleiden van inzichten uit de analyse — niet alleen het schrijven van code. Je gebruikt de songteksten van Prince als voorbeeld, maar je kunt de stappen toepassen op je eigen favoriete artiest. Je reis door de volledige tutorialserie moet hopelijk je verwondering prikkelen over de mogelijkheden van tekstuele (lyrische) analyse, terwijl je wordt ingewijd in Natural Language Processing (NLP) en machinelearningtechnieken.
Vereisten
Deel Twee-A van deze tutorialserie vereist een basisbegrip van tidy data — specifiek pakketten zoals
dplyrvoor datatransformatie,ggplot2voor visualisaties, en de%>%pipe-operator (oorspronkelijk uit hetmagrittr-pakket). Je ziet dat verschillende stappen vaak gecombineerd worden met de%>%-operator om de code compact en herbruikbaar te houden.Omdat deze tutorial ook een casestudy is, zit hij vol met visualisaties die verder gaan dan de gebruikelijke grafieken en diagrammen. Daarom is nog een vereiste dat je nieuwe pakketten kunt installeren in je eigen omgeving. Om te focussen op het vastleggen van inzichten, heb ik code opgenomen die is ontworpen om hergebruikt te worden op je eigen dataset, maar voor een gedetailleerde uitleg is verder onderzoek van jouw kant nodig. Voor elk pakket worden ondersteunende links gegeven. Zo maak je kennis met nieuwe tools, terwijl je focust op hun toepassing.
Tip: lees eerst Deel één om een basis te leggen voor de tutorialserie en inzicht te krijgen in de dichte en diverse woordenschat van de songteksten van Prince.
Overzicht van sentimentanalyse
Methode: Sentimentanalyse is een vorm van textmining die erop gericht is de opinie en subjectiviteit van inhoud te bepalen. Toegepast op songteksten kunnen de resultaten niet alleen representatief zijn voor de houding van de artiest, maar ook wijdverspreide culturele invloeden onthullen. Er zijn verschillende methoden voor sentimentanalyse, waaronder het trainen op een bekende dataset, het opstellen van eigen classificatieregels en het gebruik van vooraf gedefinieerde lexicale woordenboeken (lexicons). In deze tutorial gebruik je de lexicongebaseerde aanpak, maar ik raad je aan de andere methoden en hun afwegingen ook te onderzoeken.
Niveaus: Net zoals er verschillende methoden zijn voor sentimentanalyse, zijn er ook verschillende niveaus van analyse op basis van de tekst. Deze niveaus zijn meestal document, zin en woord. Bij songteksten kan het document worden gedefinieerd als sentiment per decennium, jaar, chartniveau of nummer. Zinsniveau is bij songteksten vaak geen optie, omdat interpunctie rijm en patronen kan verstoren. Woordniveau-analyse onthult gedetailleerde informatie en kan dienen als basiskennis voor meer geavanceerde praktijken in topic modeling.
Bereid je vragen voor!
Elk data science-project moet een set vragen hebben om te onderzoeken. Houd de volgende vragen in gedachten terwijl je met deze tutorial aan de slag gaat: is het mogelijk een programma te schrijven dat de stemming in songteksten bepaalt? Zijn vooraf gedefinieerde lexicons voldoende? Hoeveel datapreparatie is nodig? Kun je je resultaten koppelen aan echte gebeurtenissen? Verandert sentiment in de tijd? Zijn hits positiever of negatiever dan nummers die niet in de hitlijsten staan? Welke woorden springen eruit in de songteksten in het jaar waarin gezegd wordt dat Prince 9/11 voorspelde? Voorspelde hij zijn eigen dood?
Tip: lyrische analyse is heel anders en meestal complexer dan toespraken of boeken, waardoor het lastig is om inzichten te vangen. Wees dus voorzichtig met alle aannames! In deze tutorial zal ik meer vragen dan antwoorden aandragen, dus wees bereid om buiten de rechthoekige parallellogram te denken!
Voorbereidend werk
Libraries en functies
Om te beginnen met het analyseren van de songteksten van Prince, laad je de onderstaande libraries. Dit lijkt misschien veel, maar de meeste zijn voor grafieken en diagrammen. Gezien het frequente gebruik van visuals is het prettig om een standaardkleurschema te definiëren voor consistentie. Ik heb een lijst gemaakt met specifieke ANSI-kleuren. De functie
theme()uitggplot2maakt het mogelijk afzonderlijke grafieken aan te passen, dus maak je ook je eigen functie,theme_lyrics(), die de standaardinstellingen wijzigt. Het pakketknitris een engine voor dynamische rapportgeneratie met R. Gebruik dit samen metkableExtraenformattableom aantrekkelijke tekttabellen met kleur te maken. Maak opnieuw je eigen functie,my_kable_styling(), om de output van deze libraries te standaardiseren. De andere pakketten noem ik zodra ze gebruikt worden.Sushi-data
Zoals altijd begin je met het inlezen van de ruwe data in een data frame. In Deel één heb je de oorspronkelijke dataset opgeschoond, zoals het uitschrijven van samentrekkingen, het verwijderen van escape-sequenties en het omzetten van tekst naar kleine letters. Je hebt die schone dataset opgeslagen naar een CSV-bestand voor gebruik in deze tutorial. Gebruik
read.csv()omprince_datate maken en houd in gedachten dat je songteksten tekenreeksen moeten zijn, dus zorg dat jestringsAsFactors = FALSEzet. Omdat je de rijnummers niet nodig hebt, zet jerow.names = 1.Let op dat je deze parameters kunt vermijden door dit in te lezen in een modern data frame, een tibble, met
read_csv(), maar voor consistentie met Deel één wordt hierread.csv()gebruikt.Kijk snel naar de data:
Je ziet dat
prince_dataeen data frame is met 824 nummers en 10 kolommen. Dat betekent dat een record letterlijk een nummer is!Schone pret:
prince_tidyHier zijn nog een paar datamanipulatiestappen:
Om je ruwe data in een opgeruimd formaat te zetten, gebruik je
unnest_tokens()uittidytextomprince_tidyte maken, dat de songteksten opbreekt in losse woorden met één woord per rij. Daarna kun jeanti_join()enfilter()uitdplyrgebruiken voor de resterende schoonmaakstappen. (Zie Deel één voor een gedetailleerde uitleg.)Je nieuwe dataset,
prince_tidy, is nu in een getokeniseerd formaat met één woord per rij, samen met het nummer waaruit het komt. Je hebt nu een data frame van 76116 woorden en 10 kolommen.Beschrijvende statistiek
Als je Deel één nog niet hebt gelezen, wil je misschien snel naar een paar samenvattende grafieken van de volledige dataset kijken. Dat doe je met creatieve grafieken uit de pakketten
ggplot2,circlizeenyarrr.Opgeruimd en wel: woordenaantal per nummer
Een piraat zou shipshape zeggen wanneer alles netjes op orde is. Hier is dus een interessante weergave van de schone, opgeruimde data die de lexicale diversiteit toont, of anders gezegd: de woordenschat van de songteksten in de tijd. Een pirate plot is een geavanceerde methode om een continue afhankelijke variabele, zoals het aantal unieke woorden, uit te zetten als functie van een categorische onafhankelijke variabele, zoals decennium. Dit combineert ruwe datapunten, beschrijvende en inferentiële statistiek in één effectieve plot. Bekijk deze mooie blog voor meer details over
pirateplot()uit het pakketyarrr.Maak het data frame
word_summarydat het aantal onderscheidende woorden per nummer berekent. Hoe diverser de songtekst, hoe groter de woordenschat. Zo naar de data kijken bereidt je voor op analyse op woordniveau. Zet het decenniumveld op de waarde"NONE"voor nummers zonder releasedatum en herlabel die velden met schonere labels metselect().Elke gekleurde cirkel in deze pirate plot staat voor een nummer. Het dichte rode gebied met de waarde
"NONE" laat zien dat een groot aantal nummers in de dataset geen releasedatum heeft. Er is een lichte stijgende trend in het unieke aantal woorden per nummer in de vroege decennia: de vaste horizontale lijn toont het gemiddelde aantal woorden voor dat decennium. Dit is belangrijk om te weten als je het sentiment in de tijd gaat analyseren. De woorden worden door de loop van Prince’ carrière steeds veelzeggender.Ik daag je uit om pirate plots verder te verkennen, want je hebt met deze grafiek nog maar het oppervlak aangeraakt!
Het hele jaar door: aantal nummers per jaar
Circulaire grafieken zijn een unieke manier om complexe (of simpele!) relaties tussen meerdere categorieën te visualiseren. (En lp’s waren rond!). De onderstaande grafiek is simpelweg een circulaire staafgrafiek met
coord_polar()uitggplot2die het relatieve aantal nummers per jaar toont. Het is veel code voor zo’n eenvoudige grafiek, maar absoluut de moeite. Een soortgelijk voorbeeld vind je uitgebreider in de R Graph Gallery hier. Net als bij pirate plots is dit slechts een introductie tot wat je kunt doen met circulaire plots.Zie je de opening helemaal bovenaan? Dit wijst op de honderden nummers zonder releasedata die je in de pirate plot zag. Met zo’n groot aantal niet-uitgebrachte nummers zou de grafiek onbruikbaar worden als je ze zou meenemen. De ontbrekende jaren geven de jaren aan waarin geen nummers zijn uitgebracht. De meest productieve jaren waren 1996 en 1998.
Wil je weten waarom? Lees verder!
Akkoorden: nummers in de hitlijsten per decennium
De volgende grafiek laat de relatie zien tussen het decennium waarin een nummer werd uitgebracht en of het de Billboard-hitlijsten haalde. Een
chordDiagram()gebruiken voor muzikale analyse leek gewoon passend! Deze grafische tool komt uit het prachtigecirclize-pakket van Zuguang Gu. De grafiek is opgesplitst in twee categorieën: chartstatus (boven) en decennium (onder). De twee categorieën zijn gescheiden door brede openingen, met kleinere openingen tussen de waarden. De algemene opmerkingen in de onderstaande code kun je hier aanvullen met meer detail.De bovenstaande cirkelgrafiek lijkt op het eerste gezicht complex, maar laat mooi de aantallen nummers per decennium per chartniveau zien. Je ziet dat Prince zijn carrière in de jaren zeventig begon met slechts enkele releases, waarvan sommige de hitlijsten haalden. Vergelijk je de jaren tachtig met de jaren negentig, dan zie je dat er in de jaren negentig meer nummers zijn uitgebracht, maar dat in de jaren tachtig meer nummers de hitlijsten haalden. Er waren slechts enkele commercieel succesvolle nummers in de jaren 2000 en in de jaren 2010 waren er geen hits.
Lexicons en lyrics
In deze sectie ga je:
Verken sentimentlexicons
Het pakket
tidytextbevat een dataset genaamdsentimentsmet verschillende afzonderlijke lexicons. Deze lexicons zijn woordenboeken met woorden waaraan een sentimentcategorie of -waarde is toegekend.tidytextbiedt drie lexicons voor algemeen gebruik:Om de lexicons te bekijken, maak je een data frame
new_sentiments. Filter een financieel lexicon eruit, maak voor het AFINN-lexicon een binaire (ook wel polair genoemde) sentimentkolom door de numerieke score om te zetten naar positive of negative, en voeg een veld toe met het aantal verschillende woorden per lexicon.new_sentimentsheeft één kolom met de verschillende sentimentcategorieën. Voor een beter beeld van de woordenaantallen per lexicon, per categorie, gebruik jespread()uittidyrom die categorieën te pivoteren naar afzonderlijke velden. Profiteer van de pakkettenknitrenkableExtrain de eerder gemaakte functiemy_kable_styling(). Voeg kleur toe aan je tabel metcolor_tile()encolor_bar()uitformattableom een mooi geformatteerde tabel te maken. Print je tabel en bekijk de verschillen tussen de lexicons.De bovenstaande tabel geeft je een idee van de omvang en structuur van elk lexicon.
Match dot common
Om te bepalen welk lexicon het meest toepasbaar is op de songteksten, wil je kijken naar de matchratio van woorden die zowel in het lexicon als in de lyrics voorkomen. Ter herinnering: er zijn 76116 totale woorden en 7851 verschillende woorden in
prince_tidy.Hoeveel van die woorden staan er daadwerkelijk in de lexicons?
Gebruik een
inner_join()tussenprince_tidyennew_sentimentsen groepeer dan per lexicon. Het NRC-lexicon heeft 10 verschillende categorieën en een woord kan in meer dan één categorie voorkomen: woorden kunnen bijvoorbeeld negatief en verdrietig zijn. Dat betekent dat jen_distinct()insummarise()wilt gebruiken om het aantal verschillende woorden per lexicon te krijgen.Het NRC-lexicon bevat meer van de verschillende woorden uit de songteksten dan AFINN of Bing. Merk op dat de som van de matchratio’s laag is. Geen enkel lexicon kan alle woorden bevatten, en dat hoeft ook niet. Veel woorden zijn neutraal en hebben geen bijbehorend sentiment. Bijvoorbeeld, 2000 is doorgaans een neutraal woord en staat daarom niet in de lexicons. Maar als je je herinnert, voorspelden mensen dat vliegtuigen uit de lucht zouden vallen en computers zouden stoppen met werken in dat jaar. Er is dus een bijbehorende angst in de songtekst die niet wordt vastgelegd in sentimentanalyse met standaardlexicons.
Hier zijn enkele redenen waarom een woord niet in een lexicon voorkomt:
Geloof me niet op m’n woord
Kijk naar enkele specifieke woorden uit de songteksten van Prince die waarschijnlijk impact hebben op sentiment. Staan ze in alle lexicons?
Controversy en dark komen voor in NRC en Bing, maar gangster alleen in Bing. Race komt helemaal niet voor en is een cruciaal onderwerp in Prince’ songteksten. Maar is het makkelijk te koppelen aan een sentiment? Let op dat AFINN veel kleiner is en slechts één van deze woorden bevat.
Woordvormen
Nu een ingewikkelder voorbeeld. Seksualiteit is een terugkerend thema in Prince’ muziek. Hoe wordt sentimentanalyse op basis van vooraf gedefinieerde lexicons beïnvloed door verschillende vormen van een woord? Hier zijn bijvoorbeeld alle verwijzingen naar het stamwoord sex in de songteksten. Vergelijk deze met Bing en NRC en kijk waar er matches zijn.
Merk op dat Prince sexy vaak gebruikt, maar dat dit in deze vorm niet in NRC voorkomt. Het woord sex staat wel in NRC maar niet in Bing. Wat als je naar de stammen of wortels van woorden kijkt, helpt dat? Wat een dilemma! Je tekst kan een voltooid deelwoord, een meervoud of een bijwoord van een stamwoord bevatten, maar het komt mogelijk in geen enkel lexicon voor. Hoe ga je daarmee om?
Meer datapreparatie?
Het kan nodig zijn dat je nog een paar stappen in de datapreparatie uitvoert. Hier zijn drie technieken om te overwegen voordat je sentimentanalyse doet:
Een geavanceerd concept in sentimentanalyse is dat van synoniem- (semantisch vergelijkbare “peers”) en hyperoniemvervanging (een gemeenschappelijke “overkoepelende” term). Dit zijn woorden die vaker worden gebruikt dan het gerelateerde woord in de songtekst en die wél in een lexicon voorkomen, wat zo een hogere matchpercentage oplevert. Er is niet genoeg ruimte in deze tutorial om extra datapreparatie te behandelen, maar het is zeker iets om te overwegen!
Uitdaging: doe wat onderzoek naar lexicons en hoe ze worden gemaakt. Bestaat er al een dat beter geschikt is voor songteksten? Als je echt geïnteresseerd bent, overweeg dan wat er nodig is om je eigen lexicon te bouwen. Wat is het verschil tussen classifier-gebaseerde en lexicon-gebaseerde sentimentanalyse?
Gedetailleerde analyse
Nu je een basisbegrip hebt van de dataset en de lexicons, kun je die kennis toepassen door ze samen te voegen voor analyse. Dit zijn de stappen op hoofdlijnen:
Maak sentimentdatasets
Begin met het maken van Prince-sentimentdatasets voor elk van de lexicons door een
inner_join()uit te voeren op de functieget_sentiments(). Geef de naam van het lexicon bij elke aanroep door. Gebruik voor deze oefening Bing voor binaire en NRC voor categorische sentimenten. Omdat woorden in meerdere NRC-categorieën kunnen voorkomen, zoals Negative/Fear of Positive/Joy, maak je ook een subset zonder de categorieën positive en negative om later te gebruiken.In the mood: overall sentiment
In de gedetailleerde analyse van de songteksten wil je de verschillende niveaus van tekst bekijken, zoals alle nummers, chartniveau, decennium en woordniveau. Begin met het plotten van de NRC-sentimentanalyse van de hele dataset.
(Voor de leuk heb ik de pakketten
memeryenmagickgebruikt om afbeeldingen (memes) aan de grafieken toe te voegen.)Het lijkt erop dat NRC voor Prince’ songteksten sterk de positive kant op leunt. Maar staan alle woorden met een sentiment disgust of anger ook in de categorie negative? Het kan de moeite zijn om dat te checken.
Kijk nu naar het algemene sentiment volgens Bing. Van de 1185 verschillende woorden uit de songteksten van Prince die in het Bing-lexicon voorkomen: hoeveel zijn positief en hoeveel negatief?
Zou het zo kunnen zijn dat Bing ongeveer evenveel positive als negative sentiment laat zien in Prince’ muziek? Heft overall sentiment zichzelf op wanneer je naar een dataset kijkt die te groot is? Moeilijk te zeggen, maar bekijk het in stukken en kijk wat er gebeurt.
In de akoestiek bestaat fase-annulering waarbij de frequentie van twee instanties van dezelfde golf precies uit fase is en elkaar opheffen, bijvoorbeeld wanneer je een drum opneemt met twee microfoons en het geluid langer doet over een van de microfoons. Dit resulteert in totale stilte op die frequentie! Hoe zou dit kunnen gelden voor sentimentanalyse van grote datasets? Denk er eens over na.
Harder zetten: chartniveau
Zet het volume van je analyse harder door het op te splitsen naar chartniveau met het Bing-lexicon. Maak een grafiek van het polaire sentiment per chartniveau. Gebruik
spread()om de sentimenten in kolommen te splitsen enmutate()om een polarity-veld (positive - negative) en een veld percent_positive ($positive / total sentiment * 100$) te maken, voor een ander perspectief. Voeg voor de polariteitsgrafiek eenyintercepttoe metgeom_hline(). Plot de grafieken naast elkaar metgrid.arrange().Zegt dit dat nummers die de hitlijsten haalden doorgaans positiever zijn dan negatiever? Zo ja, wat zegt dit over wat het publiek wil horen? Kun je zulke aannames wel maken? Kijkend naar het positieve sentiment relatief aan het totale sentiment, lijken de genoteerde nummers net iets positiever dan negatief. Dat is interessant, gezien het Bing-lexicon zelf meer negatieve dan positieve woorden heeft.
Poolsmelting: So Blue
Omdat je naar sentiment vanuit een polair perspectief kijkt, wil je misschien zien of het weer verandert in de tijd (nerdhumor). Gebruik dit keer
geom_smooth()met de methodeloessvoor een gladdere curve en nog eengeom_smooth()met methode =lmvoor een lineaire trendlijn.Enkele extremen zijn in de tweede grafiek aangepast, maar de algemene trend van de polariteit in de tijd is in beide gevallen negatief.
Sfeerring
Je gebruikt opnieuw de kracht van de
chordDiagram()om de relaties tussen NRC-sentimenten en decennia te bekijken. Merk op dat de sentimentcategorieën bovenin de ring staan en de decennia onderin.Dit toont de aantallen woorden per NRC-categorie per decennium. Het is veel om in één kleine grafiek te verwerken, maar het geeft enorm veel informatie over relaties tussen categorieën en tijd. Deze diagrammen zijn ontzettend aanpasbaar en kunnen zo eenvoudig of informatief zijn als je wilt.
Sentiment in real time
Gegeven al die heerlijke cursussen die je op DataCamp hebt gevolgd, sta je vast te trappelen om die skills toe te passen op échte situaties. Doe dat nu door je analyse van Prince’ songteksten te mappen op iets echts gedurende een bepaalde tijd. Wees echter sceptisch en voorzichtig wanneer je een vereenvoudigde analyse toepast op zo’n complex onderwerp.
Ik heb een lijst met levensgebeurtenissen van Prince gemaakt, verzameld uit populaire bronnen zoals Rolling Stone, Biography.com, enz. Ik heb publiekelijk bekende jaren gekozen die overeenkomen met nummers met releasedata in onze dataset. Lees die events nu in uit
princeEvents.csv. Gebruik vervolgensprince_bingenspread()om een polariteitsscore per jaar te maken. Join met het data frameeventsen maak een sentimentveld zodat je kleuren kunt vullen in je staafdiagram. Gebruik zoals altijdcoord_flip()bij grote tekstlabels.Tip: je vindt
princeEvents.csvhier.Ik vind het fascinerend om de gebeurtenissen met het sentiment te vergelijken. Hoewel subjectief, vond ik ze sterk gecorreleerd. Toegegeven, ik heb de eventdataset zelf samengesteld. Ik had hem gemakkelijk kunnen bevooroordelen om te matchen met de songteksten. Maar ik heb wel meerdere bronnen per jaar gebruikt om het meest gerapporteerde feit te bepalen. Deze resultaten zouden je moeten motiveren om nog dieper in een woordniveau-analyse te duiken om te zien waarover Prince zong.
The Black Album: 1994 - 1996
In 1994 bracht Prince The Black Album uit in een poging zijn Afro-Amerikaanse publiek terug te winnen. Gezien het feit dat 1994 en de twee daaropvolgende jaren opvallen in de analyse, kun je kijken naar de topwoorden die matchen met het NRC-lexicon voor deze periode met
geom_label_repel()uit het pakketggrepelsamen metgeom_point(). Dit is een lastige grafiek, dus ik raad aan met de configuratie van de onderstaande code te spelen.In 1994 zei Prince tegen Rolling Stone magazine,
Hij verscheen dus in het openbaar met het woord "slave" prominent op zijn gezicht geschreven en verklaarde openlijk zijn anticorporatieve sentiment richting zijn platenlabel. Hij veranderde ook zijn naam in een symbool. Hoe goed matchen de bovenstaande woorden met deze gebeurtenissen? Zie je de woorden "slave", "emancipation", "black" en "change" in de grafiek?
(Onthoud, dit gaat niet alleen over Prince. Je kunt dit toepassen op elke tekst! Bijvoorbeeld: wat waren de meest trust-woorden die president Trump sprak in zijn eerste ambtsjaar?)
Dit keer persoonlijk: 1998
De grafiek Sentiment by Events hierboven gaf aan dat Prince tussen 1996 en 1998 trouwde, twee kinderen verloor en naar verluidt 9/11 op het podium voorspelde. (Zoek op YouTube naar zijn concert in 1998 in Nederland en luister zelf!) Gebruik dezelfde stappen als bij de vorige grafiek en bekijk de top 10 woorden in elke NRC-categorie in 1998.
Gezien het persoonlijke verlies en de dreigende terreurdreiging zijn woorden als "suicide", "waste", "mad", "hurt" en vele andere die niet in deze grafiek zijn opgenomen zeer kenmerkend voor de gebeurtenissen. Deze woorden zijn krachtig en geven echt inzicht in het sentiment van die periode.
Op de radar: radardiagrammen
Een andere goede manier om sentimenten over categorieën te vergelijken, is met een radardiagram, ook wel spinnenwebdiagram genoemd. Je kunt dit soort grafieken maken met het pakket
radarchart. Ze zijn handig om te zien welke variabelen vergelijkbare waarden hebben of of er uitschieters zijn per variabele.Je splitst deze analyse op in drie niveaus: jaar, chart en decennium. (Om ruimte te besparen laat ik alleen de code voor de specifieke jaren zien.)
prince_nrc_subdie de sentimenten positive en negative niet bevat, zodat de andere duidelijker zichtbaar zijn. Bereken dit keer eerst het totale aantal woorden per sentiment per jaar, evenals het totale sentiment voor het hele jaar en verkrijg een percentage ($aantal sentimentwoorden per jaar / totaal per jaar * 100$).select().spread()-en naar meerdere kolommen, zodat je één rij per sentiment hebt en een kolom per jaar. Gebruik danchartJSRadar()om een interactieve HTML-widget te genereren. Je kunt een argument meegeven om datasetlabels in de mouse-over te tonen. (FYI, soms worden de J en Y van het woord "joy" doorradarchartafgesneden en lijkt het op "iov".)Je moet even scrollen om deze grafieken te vergelijken omdat ik ze op volledige grootte wilde laten. Interessant is dat je met een kleinere dataset zoals
jaarde variatie in elk sentiment veel duidelijker ziet met dit type visualisatie. Je valt mogelijk ook op dat, in tegenstelling tot eerdere oefeningen, bij gebruik van percentages "joy" de hoogste score heeft op alle grafieken (vooral aan het begin van Prince’ carrière in 1978).Sign O' the Times
Spotify gebruikt machine learning om aanbevelingen te doen voor zijn 140 miljoen actieve gebruikers. Het gebruikt songteksten nog niet. Stel je voor dat het dat wel zou doen... Neem nu een nog diepere kijk in de sfeer van specifieke nummers.
In 1987 schreef Prince een nummer genaamd "Sign O' the Times". Een Billboard-artikel van Kenneth Partridge stelde:
Hoe zou een machine de sfeer van dit nummer interpreteren? Is het sterk emotioneel of simpelweg informatief? Vertegenwoordigt het Prince als persoon of de samenleving in het algemeen? Plot de NRC-categorieën met
ggplot2.Hoewel opnieuw sterk subjectief, lijken je resultaten te bevestigen dat Prince "cool en afstandelijk" blijft, zoals Partridge stelt. Dat kun je afleiden uit de observatie dat anticipation-woorden vaker voorkomen dan emotionele categorieën als sadness, fear en anger. Hier is een artikel dat stelt dat deze drie categorieën emoties zijn, terwijl anticipation een sentiment is. De Billboard-auteur stelde ook dat het nummer simpelweg een "statusupdate, bijna zonder emotie" is. Het lijkt erop dat de machine het daarmee eens is. Interpreteer jij het ook zo? Ik weet dat dit een R-tutorial is, maar sentimentanalyse is niet puur technisch. Men zegt dat het de plek is waar AI psychologie ontmoet.
Gebruik
ggplot2om in een iets andere grafiek naar de woorden per categorie te kijken.Kun je deze woorden koppelen aan de onderwerpen in het Billboard-artikel? De connectie is vrij duidelijk: Challenger-ramp -> "rocket", AIDS -> "disease", drugs -> "crack", natuurrampen -> "hurricane", bendes -> "gang", enz.
Het is bijna alsof Partridge eerst wat R-code schreef en deze grafiek maakte voordat hij het artikel schreef!
Meer nummers
Bekijk de sentimentcategorieën voor nog een paar nummers met sprekende titels en kijk of ze gecorreleerd lijken.
NRC-sentimenten tonen hoge anticipation en fear voor een nummer over the future, en hetzelfde plus hoge trust voor een nummer over controversy. De nummers die overwegend sad zijn, lijken eveneens bij hun titels te passen.
Bigrams per decennium
Tot nu toe heb je alleen gekeken naar unigrams of losse woorden. Maar als "love" een veelvoorkomend woord is, wat gaat eraan vooraf? Of volgt erop? Losse woorden uit hun context bekijken kan misleidend zijn. Tijd dus om naar bigrams of woordparen te kijken.
Handig genoeg biedt het pakket
tidytextde mogelijkheid om paren van woorden te unnesten, evenals losse woorden. In dit geval roep jeunnest_tokens()aan met het argumenttokenop ngrams. Omdat je alleen naar bigrams (twee opeenvolgende woorden) kijkt, geef jen = 2door. Gebruikprince_bigramsom de resultaten op te slaan.Het pakket
tidyrbiedt de mogelijkheid om bigrams op te splitsen in individuele woorden met de functieseparate(). Om de stopwoorden en ongewenste woorden te verwijderen, wil je de bigrams opsplitsen en filteren wat je niet wilt, en vervolgens metunite()de woordparen weer samenvoegen. Zo kun je eenvoudig de meest voorkomende bigrams per decennium visualiseren. (Zie Deel één voor een uitleg vanslice()enrow_number())Met bigrams zie je bijna hoe de veelgebruikte frasen verschuiven van sex, dance en romance naar religie en (rainbow) children. Voor het geval je het niet wist: de term "rainbow baby" wordt soms gebruikt door ouders die een volgend kind verwachten na het verlies van een baby door een miskraam. Opmerkelijk genoeg kon ik geen echte recensie van Prince’ album Rainbow Children vinden die hier melding van maakt.
Sentiment met bigrams
Hoe beïnvloeden bigrams het sentiment? Gebruik dit keer het AFINN-lexicon voor sentimentanalyse op woordparen en kijk hoe vaak sentimentgebonden woorden worden voorafgegaan door "not" of andere ontkennende woorden.
Op de eerste regel van de grafiek krijgt care een vals positief sentiment omdat de "not" wordt genegeerd bij analyse van losse woorden. Heffen de vals-positieve bigrams de vals-negatieve bigrams op?
Weer een vraag die ik niet kan beantwoorden, maar wel een goed onderwerp om verder te verkennen. Tip: je kunt ook de parameters "trigrams" en "n = 3" meegeven aan
unnest_tokens()om naar nog meer opeenvolgende woorden te kijken!Er zijn ook andere ontkenningswoorden om te overwegen. Dit keer maak je een netwerkgrafiek met de pakketten
ggraphenigraph. Je zet de woorden op in verbonden knopen met de ontkenningswoorden in het midden. Maak het eerste object uit de opgeruimde dataset metgraph_from_data_frame()en gebruik vervolgensggraph()om het te plotten. Je kunt de hoofdknopen markeren met een aanroep vangeom_edge_density(). Meer details van een soortgelijk voorbeeld vind je in het boek van Julia Silge en David Robinson over Tidy Text Mining.Hier zie je de woordparen die samenhangen met ontkenningswoorden. Als je analyse is gebaseerd op unigrams en "alone" komt negatief terug, zal het bigram "not alone", zoals je hierboven ziet, het omgekeerde effect hebben. Sommige woorden kruisen meerdere knopen, wat in een visual als deze goed te zien is: bijvoorbeeld "never hurt" en "not hurt".
Pairwise-vergelijkingen
Nu je naar n-grams hebt gekeken, bekijk je de correlatie tussen woorden. Welke woorden zijn het sterkst gecorreleerd? Gebruik de functie
pairwise_count()uit het pakketwidyrom co-occurrence-tellingen te identificeren. Dat wil zeggen: je telt hoe vaak elk woordpaar samen voorkomt binnen een nummer. Het pakketwidyrneemt een opgeruimde dataset en verbreedt die tijdelijk voordat het deze weer terugbrengt naar een opgeruimde structuur voor visualisatie en verdere analyse.Om het simpel te houden, heb ik vier interessante woorden uit Prince’ songteksten gekozen.
Vergelijk dat met pairwise correlation. Dit verwijst naar hoe vaak woorden samen voorkomen relatief aan hoe vaak ze afzonderlijk voorkomen. Gebruik
pairwise_cor()om de correlatie tussen woorden te bepalen op basis van hoe vaak ze in hetzelfde nummer voorkomen.Ik vind dit fascinerende inzichten in de songteksten, alleen al op basis van deze vier woorden! Als je naar deze resultaten kijkt, beginnen er thema’s naar voren te komen. Dit vormt een mooie opstap naar de volgende tutorial over topic modeling!
Conclusie
In deze tutorial heb je een opgeruimde dataset gemaakt en basisinformatie geanalyseerd zoals de lexicale diversiteit en het aantal nummers per uitgiftejaar, en je bekeek de relatie tussen uitgifte-decennium en of een nummer de hitlijsten haalde. Je hebt vervolgens enkele sentimentlexicons verkend en gekeken hoe goed ze matchten met de songteksten. Daarna heb je sentimentanalyse uitgevoerd over alle nummers in de dataset, sentiment in de tijd, sentimentscores per nummer en de impact van bigrams. Dit deed je met een brede variatie aan interessante grafieken, die elk een ander perspectief geven.
Is het dus mogelijk een programma te schrijven om stemming in songteksten te bepalen? Jazeker! Hoe betrouwbaar zijn je resultaten? Dat hangt af van een breed scala aan criteria, zoals de hoeveelheid datapreparatie, de keuze van het lexicon, de analysemethode, de kwaliteit van de brondata, enzovoort. Het vergelijken met echte gebeurtenissen, zowel persoonlijk als maatschappelijk, kan de stemming van elke songtekst verduidelijken. Prince’ polaire sentiment leek in de tijd licht te dalen, maar overall lijkt joy eruit te springen. Nummers die de hitlijsten halen lijken positiever dan nummers die dat niet doen. De claims dat hij 9/11 en zijn eigen dood voorspelde, lijken griezelig goed te rijmen met zijn woorden.
Maar songteksten zijn complex en te veel aannames kunnen problemen veroorzaken. Als je meeneemt wat je over de songteksten hebt geleerd naar de volgende tutorial, Deel Twee-B over topic modeling, ben je goed op weg om thema’s en motieven uit te graven. Hoeveel NLP kun je toepassen op songteksten die zonder interpunctie of zinsstructuur verschijnen? Waar zong Prince nu echt over? Nu je de stemming begrijpt, ben je beter voorbereid om de mogelijke topics te onderzoeken. En tot slot: kun je machinelearningtechnieken toepassen om het decennium of het chartniveau van een nummer te voorspellen? Ga mee naar de volgende tutorials om het te ontdekken!
Ik hoop dat je de reis tot nu toe leuk vond. In Deel Twee-B gaan we feesten alsof het 1999 is!