Een belangrijke set met statistieken in text mining heeft te maken met de frequentie van woorden (of andere tokens) in een bepaalde corpus van tekstdocumenten. Je kunt echter ook een extra set met statistieken gebruiken in gevallen waar elk document een bijbehorende numerieke waarde heeft die een bepaald kenmerk van het document beschrijft.
Enkele voorbeelden:
Tweets en hun respectieve aantal engagements.
URL’s en hun pageviews en bounces.
Filmtitels en hun boxoffice-omzet.
Zoekwoorden en hun impressies, klikken en conversies.
In deze tutorial
Doorloop je eerst het proces van het maken van een eenvoudige functie die de absolute en gewogen voorkomen van woorden in een corpus van documenten berekent en vergelijkt. Dit kan soms verborgen trends en totalen blootleggen die niet per se duidelijk zijn als je alleen naar de top tien waarden kijkt. Ze kunnen ook vaak afwijken van de absolute woordfrequentie.
Vervolgens bekijk je een dataset uit de praktijk (filmtitels en de boxoffice-omzet), in de hoop verborgen trends te ontdekken. Een teaser: liefde komt hoe dan ook bovendrijven!
Je gebruikt Python als programmeertaal en zet de datastructuur defaultdict uit de module collections in voor het zware werk, evenals pandas-DataFrames om de uiteindelijke output te beheren.
Absolute en gewogen woordfrequentie: introductie
Stel dat je twee tweets hebt en dat hun inhoud en aantal vertoningen (views) er als volgt uitzien:
Tweet-tekst
Views
spain
800
france
200
Het is eenvoudig om de basisanalyse te doen en vast te stellen dat je woorden 50:50 zijn verdeeld tussen ‘france’ en ‘spain’. In veel gevallen is dit alles wat je hebt, en kun je alleen de absolute frequentie van woorden meten en proberen bepaalde relaties af te leiden. In dit geval heb je wat data over elk van de documenten.
De gewogen frequentie is hier duidelijk anders, en de verdeling is 80:20. Met andere woorden, hoewel ‘spain’ en ‘france’ elk één keer voorkwamen in je tweets, verscheen de eerste vanuit het perspectief van je lezers 800 keer, terwijl de laatste 200 keer verscheen. Dat is een groot verschil!
Eenvoudige woordfrequentie met defaultdict
Beschouw nu dit iets uitgebreidere voorbeeld voor een vergelijkbare set documenten:
Document
Views
france
200
spain
180
spain beaches
170
france beaches
160
spain best beaches
160
Je loopt nu door de documenten, splitst ze in woorden en telt de voorkomens van elk van de woorden:
from collections import defaultdict
import pandas as pd
text_list =['france','spain','spain beaches','france beaches','spain best beaches']
word_freq = defaultdict(int)for text in text_list:for word in text.split():
word_freq[word]+=1
pd.DataFrame.from_dict(word_freq, orient='index') \
.sort_values(0, ascending=False) \
.rename(columns={0:'abs_freq'})Mogelijk gemaakt door
abs_freq
spain
3
beaches
3
france
2
best
1
In de bovenstaande loop doorloop je in de eerste regel text_list één voor één. De tweede regel (binnen elk document) loopt door de woorden van elk item, gesplitst op het spatie-teken (dit had ook elk ander teken kunnen zijn (‘-’, ‘,’, ‘_’, enz.)).
Wanneer je een waarde probeert toe te kennen aan word_freq[word] zijn er twee mogelijke scenario’s:
De sleutel word bestaat: in dat geval wordt de toekenning gedaan (er wordt één bij opgeteld)
De sleutel word staat niet in word_freq; in dit geval roept defaultdict de standaardfunctie aan die eraan is toegewezen bij de eerste definitie, in dit geval int.
Wanneer int wordt aangeroepen, retourneert die nul. Nu bestaat de sleutel, is de waarde nul en is deze klaar om er 1 aan toe te voegen.
Hoewel het topwoord ‘france’ was in de eerste tabel, zien we na het tellen van alle woorden binnen elk document dat ‘spain’ en ‘beaches’ samen op de eerste plaats staan. Dit is belangrijk om verborgen trends te ontdekken, vooral wanneer de lijst met documenten waarmee je werkt, in de tientallen of honderdduizenden loopt.
Gewogen woordfrequentie
Nu je de voorkomens van elk woord in de corpus van documenten hebt geteld, wil je de gewogen frequentie zien. Dat wil zeggen: je wilt zien hoe vaak de woorden in de ogen van je lezers verschenen, vergeleken met hoe vaak jij ze gebruikte.
In de eerste tabel was de absolute frequentie van de woorden gelijk verdeeld tussen ‘spain’ en ‘france’, maar ‘spain’ had duidelijk veel meer gewicht, omdat de waarde 800 was, tegenover 200 voor ‘france’.
Maar wat zou de gewogen woordfrequentie zijn voor de tweede, iets complexere, tabel?
Laten we het uitzoeken!
Je kunt een deel van de code die je hierboven gebruikte hergebruiken, maar met enkele toevoegingen:
# default value is now a list with two ints
word_freq = defaultdict(lambda:[0,0])# the `views` column you had in the first DataFrame
num_list =[200,180,170,160,160]# looping is now over both the text and the numbersfor text, num inzip(text_list, num_list):for word in text.split():# same as before
word_freq[word][0]+=1# new line, incrementing the numeric value for each word
word_freq[word][1]+= num
columns ={0:'abs_freq',1:'wtd_freq'}
abs_wtd_df = pd.DataFrame.from_dict(word_freq, orient='index') \
.rename(columns=columns) \
.sort_values('wtd_freq', ascending=False) \
.assign(rel_value=lambda df: df['wtd_freq']/ df['abs_freq']) \
.round()
abs_wtd_df.style.background_gradient(low=0, high=.7, subset=['rel_value'])Mogelijk gemaakt door
abs_freq
wtd_freq
rel_value
spain
3
510
170
beaches
3
490
163
france
2
360
180
best
1
160
160
Enkele observaties:
Hoewel ‘france’ de hoogste frase was in het geheel, lijken ‘spain’ en ‘beaches’ prominenter wanneer je de gewogen frequentie meeneemt.
rel_value is een eenvoudige deling om de waarde per voorkomen van elk woord te krijgen.
Kijkend naar rel_value zie je ook dat, hoewel ‘france’ vrij laag staat op de wtd_freq-metric, er potentie in lijkt te zitten, omdat de waarde per voorkomen hoog is. Dit kan bijvoorbeeld een hint zijn om je content over ‘france’ uit te breiden.
Je kunt ook andere statistieken toevoegen die de percentages en cumulatieve percentages van elk type frequentie laten zien, zodat je beter kunt inschatten hoeveel woorden het grootste deel van het totaal vormen, als dat al zo is:
abs_wtd_df.insert(1,'abs_perc', value=abs_wtd_df['abs_freq']/ abs_wtd_df['abs_freq'].sum())
abs_wtd_df.insert(2,'abs_perc_cum', abs_wtd_df['abs_perc'].cumsum())
abs_wtd_df.insert(4,'wtd_freq_perc', abs_wtd_df['wtd_freq']/ abs_wtd_df['wtd_freq'].sum())
abs_wtd_df.insert(5,'wtd_freq_perc_cum', abs_wtd_df['wtd_freq_perc'].cumsum())
abs_wtd_df.style.background_gradient(low=0, high=0.8)Mogelijk gemaakt door
abs_freq
abs_perc
abs_perc_cum
wtd_freq
wtd_freq_perc
wtd_freq_perc_cum
rel_value
spain
3
0.333333
0.333333
510
0.335526
0.335526
170
beaches
3
0.333333
0.666667
490
0.322368
0.657895
163
france
2
0.222222
0.888889
360
0.236842
0.894737
180
best
1
0.111111
1
160
0.105263
1
160
Met meer data valt er meer te analyseren, en kom je doorgaans op meer verrassingen uit.
Hoe ziet dit er uit in een realistische setting met echte data?
Je bekijkt filmtitels, ziet welke woorden het meest in de titels voorkomen — dat is de absolute frequentie — en welke woorden geassocieerd zijn met de meeste omzet, oftewel de gewogen frequentie.
Boxoffice Mojo heeft een lijst van meer dan 15.000 films, samen met hun bijbehorende boxoffice-omzet en rangen. Begin met het scrapen van de data met requests en BeautifulSoup — Je kunt Boxoffice Mojo hier alvast verkennen als je wilt:
final_list =[]for i inrange(1,156):ifnot i%10:print(i)
page ='http://www.boxofficemojo.com/alltime/domestic.htm?page='+str(i)+'&p=.htm'
resp = requests.get(page)
soup = BeautifulSoup(resp.text,'lxml')# trial and error to get the exact positions
table_data =[x.text for x in soup.select('tr td')[11:511]]# put every 5 values in a row
temp_list =[table_data[i:i+5]for i inrange(0,len(table_data[:-4]),5)]for temp in temp_list:
final_list.append(temp)Mogelijk gemaakt door
Je ziet dat sommige numerieke waarden speciale tekens bevatten ($, , en ^), en dat sommige waarden eigenlijk N/A zijn. Dus die moet je aanpassen:
na_year_idx =[i for i, x inenumerate(final_list)if x[4]=='n/a']# get the indexes of the 'n/a' values
new_years =[1998,1999,1960,1973]# got them by checking onlineprint(*[(i, x)for i, x inenumerate(final_list)if i in na_year_idx], sep='\n')print('new year values:', new_years)Mogelijk gemaakt door
(8003,['8004','Warner Bros. 75th Anniversary Film Festival','WB','$741,855','n/a'])(8148,['8149','Hum Aapke Dil Mein Rahte Hain','Eros','$668,678','n/a'])(8197,['8198','Purple Moon (Re-issue)','Mira.','$640,945','n/a'])(10469,['10470','Amarcord','Jan.','$125,493','n/a'])newyearvalues:[1998,1999,1960,1973]Mogelijk gemaakt door
for na_year, new_year inzip(na_year_idx, new_years):
final_list[na_year][4]= new_year
print(final_list[na_year], new_year)Mogelijk gemaakt door
['8004','Warner Bros. 75th Anniversary Film Festival','WB','$741,855',1998]1998['8149','Hum Aapke Dil Mein Rahte Hain','Eros','$668,678',1999]1999['8198','Purple Moon (Re-issue)','Mira.','$640,945',1960]1960['10470','Amarcord','Jan.','$125,493',1973]1973Mogelijk gemaakt door
Nu zet je de lijst om in een pandas-DataFrame door de kolommen de juiste namen te geven en te converteren naar de datatypes die je wilt.
import re
regex ='|'.join(['\$',',','\^'])
columns =['rank','title','studio','lifetime_gross','year']
boxoffice_df = pd.DataFrame({'rank':[int(x[0])for x in final_list],# convert ranks to integers'title':[x[1]for x in final_list],# get titles as is'studio':[x[2]for x in final_list],# get studio names as is'lifetime_gross':[int(re.sub(regex,'', x[3]))for x in final_list],# remove special characters and convert to integer'year':[int(re.sub(regex,'',str(x[4])))for x in final_list],# remove special characters and convert to integer})print('rows:', boxoffice_df.shape[0])print('columns:', boxoffice_df.shape[1])print('\ndata types:')print(boxoffice_df.dtypes)
boxoffice_df.head(15)Mogelijk gemaakt door
rows:15500columns:5
data types:
lifetime_gross int64
rank int64
studio object
title object
year int64
dtype: object
Mogelijk gemaakt door
lifetime_gross
rank
studio
title
year
0
936662225
1
BV
Star Wars: The Force Awakens
2015
1
760507625
2
Fox
Avatar
2009
2
681084109
3
BV
Black Panther
2018
3
659363944
4
Par.
Titanic
1997
4
652270625
5
Uni.
Jurassic World
2015
5
623357910
6
BV
Marvel's The Avengers
2012
6
620181382
7
BV
Star Wars: The Last Jedi
2017
7
534858444
8
WB
The Dark Knight
2008
8
532177324
9
BV
Rogue One: A Star Wars Story
2016
9
504014165
10
BV
Beauty and the Beast (2017)
2017
10
486295561
11
BV
Finding Dory
2016
11
474544677
12
Fox
Star Wars: Episode I - The Phantom Menace
1999
12
460998007
13
Fox
Star Wars
1977
13
459005868
14
BV
Avengers: Age of Ultron
2015
14
448139099
15
WB
The Dark Knight Rises
2012
Het woord ‘star’ is een van de toppers, omdat het in vijf van de top vijftien films voorkomt, en je weet ook dat de Star Wars-serie nog meer films heeft, waarvan er meerdere ook in de top staan.
Laten we nu de code die je ontwikkelde gebruiken en kijken hoe die werkt op deze dataset. Er is niets nieuws in de onderstaande code; je zet het simpelweg in één functie:
defword_frequency(text_list, num_list, sep=None):
word_freq = defaultdict(lambda:[0,0])for text, num inzip(text_list, num_list):for word in text.split(sep=sep):
word_freq[word][0]+=1
word_freq[word][1]+= num
columns ={0:'abs_freq',1:'wtd_freq'}
abs_wtd_df =(pd.DataFrame.from_dict(word_freq, orient='index').rename(columns=columns ).sort_values('wtd_freq', ascending=False).assign(rel_value=lambda df: df['wtd_freq']/ df['abs_freq']).round())
abs_wtd_df.insert(1,'abs_perc', value=abs_wtd_df['abs_freq']/ abs_wtd_df['abs_freq'].sum())
abs_wtd_df.insert(2,'abs_perc_cum', abs_wtd_df['abs_perc'].cumsum())
abs_wtd_df.insert(4,'wtd_freq_perc', abs_wtd_df['wtd_freq']/ abs_wtd_df['wtd_freq'].sum())
abs_wtd_df.insert(5,'wtd_freq_perc_cum', abs_wtd_df['wtd_freq_perc'].cumsum())return abs_wtd_df
word_frequency(boxoffice_df['title'], boxoffice_df['lifetime_gross']).head()Mogelijk gemaakt door
abs_freq
abs_perc
abs_perc_cum
wtd_freq
wtd_freq_perc
wtd_freq_perc_cum
rel_value
The
3055
0.068747
0.068747
67518342498
0.081167
0.081167
22100930.0
the
1310
0.029479
0.098227
32973100860
0.039639
0.120806
25170306.0
of
1399
0.031482
0.129709
30180592467
0.036282
0.157087
21572975.0
and
545
0.012264
0.141973
12188113847
0.014652
0.171739
22363512.0
2
158
0.003556
0.145529
9032673058
0.010859
0.182598
57168817.0
Niet verrassend staan de ‘stopwoorden’ bovenaan, wat bij de meeste collecties documenten het geval is. Je hebt ze ook dubbel, waarbij sommige met een hoofdletter beginnen en andere niet. Er zijn dus twee duidelijke dingen om aan te pakken:
Verwijder alle stopwoorden: je kunt dit doen door een nieuwe parameter aan de functie toe te voegen en je eigen lijst met stopwoorden aan te leveren.
Behandel alle woorden in kleine letters om duplicaten te verwijderen
Hier is een eenvoudige update van de functie (nieuwe parameter rm_words, evenals regels 6, 7 en 8):
# words will be expandeddefword_frequency(text_list, num_list, sep=None, rm_words=('the','and','a')):
word_freq = defaultdict(lambda:[0,0])for text, num inzip(text_list, num_list):for word in text.split(sep=sep):# This should take care of ignoring the word if it's in the stop wordsif word.lower()in rm_words:continue# .lower() makes sure we are not duplicating words
word_freq[word.lower()][0]+=1
word_freq[word.lower()][1]+= num
columns ={0:'abs_freq',1:'wtd_freq'}
abs_wtd_df =(pd.DataFrame.from_dict(word_freq, orient='index').rename(columns=columns ).sort_values('wtd_freq', ascending=False).assign(rel_value=lambda df: df['wtd_freq']/ df['abs_freq']).round())
abs_wtd_df.insert(1,'abs_perc', value=abs_wtd_df['abs_freq']/ abs_wtd_df['abs_freq'].sum())
abs_wtd_df.insert(2,'abs_perc_cum', abs_wtd_df['abs_perc'].cumsum())
abs_wtd_df.insert(4,'wtd_freq_perc', abs_wtd_df['wtd_freq']/ abs_wtd_df['wtd_freq'].sum())
abs_wtd_df.insert(5,'wtd_freq_perc_cum', abs_wtd_df['wtd_freq_perc'].cumsum())
abs_wtd_df = abs_wtd_df.reset_index().rename(columns={'index':'word'})return abs_wtd_dfMogelijk gemaakt door
from collections import defaultdict
word_freq_df = word_frequency(boxoffice_df['title'],
boxoffice_df['lifetime_gross'],
rm_words=['of','in','to','and','a','the','for','on','&','is','at','it','from','with'])
word_freq_df.head(15).style.bar(['abs_freq','wtd_freq','rel_value'],
color='#60DDFF')# E6E9EBMogelijk gemaakt door
word
abs_freq
abs_perc
abs_perc_cum
wtd_freq
wtd_freq_perc
wtd_freq_perc_cum
rel_value
0
2
158
0.00443945
0.00443945
9032673058
0.0137634
0.0137634
5.71688e+07
1
star
45
0.0012644
0.00570385
5374658819
0.00818953
0.0219529
1.19437e+08
2
man
195
0.00547907
0.0111829
3967037854
0.0060447
0.0279976
2.03438e+07
3
part
41
0.00115201
0.0123349
3262579777
0.00497129
0.0329689
7.95751e+07
4
movie
117
0.00328744
0.0156224
3216050557
0.00490039
0.0378693
2.74876e+07
5
3
63
0.00177016
0.0173925
3197591193
0.00487227
0.0427415
5.07554e+07
6
ii
67
0.00188255
0.0192751
3077712883
0.0046896
0.0474311
4.5936e+07
7
wars:
6
0.000168587
0.0194437
2757497155
0.00420168
0.0516328
4.59583e+08
8
last
133
0.003737
0.0231807
2670229651
0.00406871
0.0557015
2.00769e+07
9
harry
27
0.00075864
2611329714
0.00397896
0.0596805
9.67159e+07
10
me
140
0.00393369
0.027873
2459330128
0.00374736
0.0634279
1.75666e+07
11
potter
10
0.000280978
0.028154
2394811427
0.00364905
0.0670769
2.39481e+08
12
black
71
0.00199494
0.0301489
2372306467
0.00361476
0.0706917
3.34128e+07
13
-
49
0.00137679
0.0315257
2339484878
0.00356474
0.0742564
4.77446e+07
14
story
107
0.00300646
0.0345322
2231437526
0.00340011
0.0776565
2.08546e+07
Bekijk hetzelfde DataFrame eens gesorteerd op abs_freq:
(word_freq_df.sort_values('abs_freq', ascending=False).head(15).style.bar(['abs_freq','wtd_freq','rel_value'],
color='#60DDFF'))Mogelijk gemaakt door
word
abs_freq
abs_perc
abs_perc_cum
wtd_freq
wtd_freq_perc
wtd_freq_perc_cum
rel_value
26
love
211
0.00592863
0.069542
1604206885
0.00244438
0.111399
7.60288e+06
2
man
195
0.00547907
0.0111829
3967037854
0.0060447
0.0279976
2.03438e+07
24
my
191
0.00536668
0.0618994
1629540498
0.00248298
0.106478
8.53163e+06
15
i
168
0.00472043
0.0392526
2203439786
0.00335745
0.081014
1.31157e+07
0
2
158
0.00443945
0.00443945
9032673058
0.0137634
0.0137634
5.71688e+07
10
me
140
0.00393369
0.027873
2459330128
0.00374736
0.0634279
1.75666e+07
31
life
134
0.0037651
0.0764822
1534647732
0.00233839
0.123297
1.14526e+07
8
last
133
0.003737
0.0231807
2670229651
0.00406871
0.0557015
2.00769e+07
23
you
126
0.00354032
0.0565327
1713758853
0.00261131
0.103995
1.36013e+07
4
movie
117
0.00328744
0.0156224
3216050557
0.00490039
0.0378693
2.74876e+07
14
story
107
0.00300646
0.0345322
2231437526
0.00340011
0.0776565
2.08546e+07
33
night
104
0.00292217
0.0814555
1523917360
0.00232204
0.127946
1.46531e+07
19
american
93
0.00261309
0.0478505
1868854192
0.00284763
0.0932591
2.00952e+07
117
girl
90
0.0025288
0.151644
842771661
0.00128416
0.26663
9.36413e+06
16
day
87
0.00244451
2164198760
0.00329766
0.0843116
2.48758e+07
Laten we visualiseren om beide te vergelijken en de verborgen trends te zien:
import matplotlib.pyplot as plt
plt.figure(figsize=(20,8))
plt.subplot(1,2,1)
word_freq_df_abs = word_freq_df.sort_values('abs_freq', ascending=False).reset_index()
plt.barh(range(20),list(reversed(word_freq_df_abs['abs_freq'][:20])), color='#288FB7')for i, word inenumerate(word_freq_df_abs['word'][:20]):
plt.text(word_freq_df_abs['abs_freq'][i],20-i-1,
s=str(i+1)+'. '+ word +': '+str(word_freq_df_abs['abs_freq'][i]),
ha='right', va='center', fontsize=14, color='white', fontweight='bold')
plt.text(0.4,-1.1, s='Number of times the word was used in a movie title; out of 15500 movies.', fontsize=14)
plt.text(0.4,-1.8, s='Data: boxofficemojo.com Apr. 2018. Feedback: @eliasdabbas', fontsize=14)
plt.vlines(range(0,210,10),-1,20, colors='gray', alpha=0.1)
plt.hlines(range(0,20,2),0,210, colors='gray', alpha=0.1)
plt.yticks([])
plt.xticks([])
plt.title('Words Most Used in Movie Titles', fontsize=22, fontweight='bold')# =============
plt.subplot(1,2,2)# plt.axis('off')
plt.barh(range(20),list(reversed(word_freq_df['wtd_freq'][:20])), color='#288FB7')for i, word inenumerate(word_freq_df['word'][:20]):
plt.text(word_freq_df['wtd_freq'][i],20-i-1,
s=str(i+1)+'. '+ word +': '+'$'+str(round(word_freq_df['wtd_freq'][i]/1000_000_000,2))+'b',
ha='right', va='center', fontsize=14, color='white', fontweight='bold')
plt.text(0.4,-1.1, s='Alltime boxoffice revenue of all movies whos title contained the word. (Top word is "2") ', fontsize=14)
plt.text(0.4,-1.8, s='Data collection & methodology: http://bit.ly/word_freq', fontsize=14)
plt.vlines(range(0,9_500_000_000,500_000_000),-1,20, colors='gray', alpha=0.1)
plt.hlines(range(0,20,2),0,10_000_000_000, colors='gray', alpha=0.1)
plt.xlim((-70_000_000,9_500_000_000))
plt.yticks([])
plt.xticks([])
plt.title('Words Most Associated With Boxoffice Revenue', fontsize=22, fontweight='bold')
plt.tight_layout(pad=0.01)
plt.show()Mogelijk gemaakt door
Het lijkt er in elk geval in de hoofden van producenten en schrijvers op dat liefde alles overwint! Het is het meest gebruikte woord in alle filmtitels. Het staat echter niet zo hoog als het gaat om gewogen frequentie (boxoffice-omzet).
Met andere woorden: als je naar alle filmtitels kijkt, is ‘love’ het woord dat je het vaakst tegenkomt. Maar als je schat welk woord het meest verscheen in de ogen van de kijkers (waarbij boxoffice-omzet als metric wordt gebruikt), dan zouden ‘2’, ‘star’ en ‘man’ het meest bekeken zijn, of geassocieerd met de meeste omzet.
Om het duidelijk te zeggen: dit zijn heel eenvoudige berekeningen. Als je zegt dat de gewogen frequentie van het woord ‘love’ 1.604.106.767 is, betekent dat simpelweg dat de som van de lifetime boxoffice van alle films waarvan de titel het woord ‘love’ bevatte, dat bedrag was.
Het is ook interessant dat ‘2’ het topwoord is. Het is natuurlijk geen woord, maar het is een indicatie dat tweede delen van filmseries een zeer groot bedrag vertegenwoordigen. Dat geldt ook voor ‘3’, dat op de vijfde plaats staat. Merk op dat ‘part’ en ‘ii’ ook in de top tien staan, wat hetzelfde bevestigt.
‘American’ en ‘movie’ hebben een hoge relatieve waarde.
Een korte noot over de gebruikte stopwoorden in deze functie
Gewoonlijk zou je een uitgebreidere lijst met stopwoorden gebruiken dan hier, zeker als je met artikelen of socialmediaberichten werkt. Zo biedt het pakket nltk lijsten met stopwoorden in meerdere talen, die je kunt downloaden en gebruiken.
De woorden hier zijn gekozen na een paar checks op de topfilms. Veel daarvan worden meestal als stopwoorden beschouwd, maar in het geval van filmtitels was het logisch om sommige te behouden omdat ze inzicht kunnen geven. Zo kunnen de woorden ‘I’, ‘me’, ‘you’ op sociale dynamiek wijzen. Een andere reden is dat filmtitels erg korte zinnen zijn, en we daaruit zo veel mogelijk betekenis proberen te halen.
Je kunt dit zeker met je eigen set woorden proberen en licht andere resultaten zien.
Als we terugkijken naar de oorspronkelijke lijst met filmtitels, zien we dat sommige van de topwoorden niet eens in de top tien voorkomen, en dit is precies het soort inzicht dat we proberen te ontdekken met deze aanpak.
Vervolgens is het logisch om verder te kijken naar de topwoorden die interessant zijn. Laten we de films filteren die ‘2’ bevatten en kijken:
(boxoffice_df[boxoffice_df['title'].str.contains('2 | 2',case=False)]# spaces used to exclude words like '2010'.head(10))Mogelijk gemaakt door
lifetime_gross
rank
studio
title
year
15
441226247
16
DW
Shrek 2
2004
30
389813101
31
BV
Guardians of the Galaxy Vol. 2
2017
31
381011219
32
WB
Harry Potter and the Deathly Hallows Part 2
2011
35
373585825
36
Sony
Spider-Man 2
2004
38
368061265
39
Uni.
Despicable Me 2
2013
64
312433331
65
Par.
Iron Man 2
2010
79
292324737
80
LG/S
The Twilight Saga: Breaking Dawn Part 2
2012
87
281723902
88
LGF
The Hunger Games: Mockingjay - Part 2
2015
117
245852179
118
BV
Toy Story 2
1999
146
226164286
147
NL
Rush Hour 2
2001
Bekijk ook even de top-‘star’-films:
boxoffice_df[boxoffice_df['title'].str.contains('star | star',case=False)].head(10)Mogelijk gemaakt door
lifetime_gross
rank
studio
title
year
0
936662225
1
BV
Star Wars: The Force Awakens
2015
6
620181382
7
BV
Star Wars: The Last Jedi
2017
8
532177324
9
BV
Rogue One: A Star Wars Story
2016
11
474544677
12
Fox
Star Wars: Episode I - The Phantom Menace
1999
12
460998007
13
Fox
Star Wars
1977
33
380270577
34
Fox
Star Wars: Episode III - Revenge of the Sith
2005
65
310676740
66
Fox
Star Wars: Episode II - Attack of the Clones
2002
105
257730019
106
Par.
Star Trek
2009
140
228778661
141
Par.
Star Trek Into Darkness
2013
301
158848340
302
Par.
Star Trek Beyond
2016
En tot slot de top-‘man’-films:
boxoffice_df[boxoffice_df['title'].str.contains('man | man',case=False)].head(10)Mogelijk gemaakt door
lifetime_gross
rank
studio
title
year
18
423315812
19
BV
Pirates of the Caribbean: Dead Man's Chest
2006
22
409013994
23
BV
Iron Man 3
2013
35
373585825
36
Sony
Spider-Man 2
2004
48
336530303
49
Sony
Spider-Man 3
2007
53
330360194
54
WB
Batman v Superman: Dawn of Justice
2016
59
318412101
60
Par.
Iron Man
2008
64
312433331
65
Par.
Iron Man 2
2010
82
291045518
83
WB
Man of Steel
2013
176
206852432
177
WB
Batman Begins
2005
182
202853933
183
Sony
The Amazing Spider-Man 2
2014
Volgende stappen en verbeteringen
Als eerste stap kun je proberen meer woorden te verzamelen: filmtitels zijn extreem kort en brengen vaak niet de letterlijke betekenis van de woorden over. Zo is een peetvader eigenlijk iemand die getuige is van de doop van een kind en belooft voor dat kind te zorgen (of misschien een maffioso die voor zijn plezier moordt?!).
Een volgende oefening kan zijn om meer gedetailleerde beschrijvingen te verzamelen, naast de filmtitel. Bijvoorbeeld:
"A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."
vertelt ons veel meer over de film dan ‘The Matrix’.
Of je kunt een van de onderstaande paden nemen om je analyse te verrijken:
Betere statistische analyse: omgaan met extreme waarden/outliers, andere metrics gebruiken.
Text mining: vergelijkbare woorden en onderwerpen groeperen (‘happy’, ‘happiness’, ‘happily’, enz.).
Meer granulair analyseren: dezelfde functie draaien voor verschillende jaren/decennia, of voor bepaalde studio’s.
Misschien wil je de data zelf verkennen, evenals andere data:
Oké, je hebt nu de tellingen van woorden in filmtitels verkend, en het verschil gezien tussen absolute en gewogen frequenties, en hoe het weglaten van een van beide een groot deel van het beeld kan missen. Je hebt ook de beperkingen van deze aanpak gezien, en suggesties om je analyse te verbeteren.
Je hebt het proces doorlopen van het maken van een speciale functie die je eenvoudig kunt draaien om elke vergelijkbare tekstdataset met getallen te analyseren, en je weet hoe dit je begrip van dit soort datasets kan verbeteren.
Probeer het door de prestaties van je tweets te analyseren, de URL’s van je website, je Facebook-berichten, of een andere vergelijkbare dataset die je tegenkomt.
Het is misschien makkelijker om gewoon de repository te clonen met de code en het zelf te proberen.
De functie word_frequency maakt deel uit van het pakket advertools, dat je kunt downloaden en gebruiken in je werk/onderzoek.
Check het en laat me weten wat je vindt! @eliasdabbas
Duik in datawetenschap met Python en leer hoe je je data goed kunt analyseren en visualiseren. Je hoeft niet te kunnen programmeren of iets dergelijks.
Een belangrijke set met statistieken in text mining heeft te maken met de frequentie van woorden (of andere tokens) in een bepaalde corpus van tekstdocumenten. Je kunt echter ook een extra set met statistieken gebruiken in gevallen waar elk document een bijbehorende numerieke waarde heeft die een bepaald kenmerk van het document beschrijft.
Enkele voorbeelden:
In deze tutorial
defaultdictuit de modulecollectionsin voor het zware werk, evenals pandas-DataFrames om de uiteindelijke output te beheren.Absolute en gewogen woordfrequentie: introductie
Stel dat je twee tweets hebt en dat hun inhoud en aantal vertoningen (views) er als volgt uitzien:
Het is eenvoudig om de basisanalyse te doen en vast te stellen dat je woorden 50:50 zijn verdeeld tussen ‘france’ en ‘spain’. In veel gevallen is dit alles wat je hebt, en kun je alleen de absolute frequentie van woorden meten en proberen bepaalde relaties af te leiden. In dit geval heb je wat data over elk van de documenten.
De gewogen frequentie is hier duidelijk anders, en de verdeling is 80:20. Met andere woorden, hoewel ‘spain’ en ‘france’ elk één keer voorkwamen in je tweets, verscheen de eerste vanuit het perspectief van je lezers 800 keer, terwijl de laatste 200 keer verscheen. Dat is een groot verschil!
Eenvoudige woordfrequentie met
defaultdictBeschouw nu dit iets uitgebreidere voorbeeld voor een vergelijkbare set documenten:
Je loopt nu door de documenten, splitst ze in woorden en telt de voorkomens van elk van de woorden:
In de bovenstaande loop doorloop je in de eerste regel
text_listéén voor één. De tweede regel (binnen elk document) loopt door de woorden van elk item, gesplitst op het spatie-teken (dit had ook elk ander teken kunnen zijn (‘-’, ‘,’, ‘_’, enz.)).Wanneer je een waarde probeert toe te kennen aan
word_freq[word]zijn er twee mogelijke scenario’s:wordbestaat: in dat geval wordt de toekenning gedaan (er wordt één bij opgeteld)wordstaat niet inword_freq; in dit geval roeptdefaultdictde standaardfunctie aan die eraan is toegewezen bij de eerste definitie, in dit gevalint.Wanneer
intwordt aangeroepen, retourneert die nul. Nu bestaat de sleutel, is de waarde nul en is deze klaar om er 1 aan toe te voegen.Hoewel het topwoord ‘france’ was in de eerste tabel, zien we na het tellen van alle woorden binnen elk document dat ‘spain’ en ‘beaches’ samen op de eerste plaats staan. Dit is belangrijk om verborgen trends te ontdekken, vooral wanneer de lijst met documenten waarmee je werkt, in de tientallen of honderdduizenden loopt.
Gewogen woordfrequentie
Nu je de voorkomens van elk woord in de corpus van documenten hebt geteld, wil je de gewogen frequentie zien. Dat wil zeggen: je wilt zien hoe vaak de woorden in de ogen van je lezers verschenen, vergeleken met hoe vaak jij ze gebruikte.
In de eerste tabel was de absolute frequentie van de woorden gelijk verdeeld tussen ‘spain’ en ‘france’, maar ‘spain’ had duidelijk veel meer gewicht, omdat de waarde 800 was, tegenover 200 voor ‘france’.
Maar wat zou de gewogen woordfrequentie zijn voor de tweede, iets complexere, tabel?
Laten we het uitzoeken!
Je kunt een deel van de code die je hierboven gebruikte hergebruiken, maar met enkele toevoegingen:
Enkele observaties:
rel_valueis een eenvoudige deling om de waarde per voorkomen van elk woord te krijgen.rel_valuezie je ook dat, hoewel ‘france’ vrij laag staat op dewtd_freq-metric, er potentie in lijkt te zitten, omdat de waarde per voorkomen hoog is. Dit kan bijvoorbeeld een hint zijn om je content over ‘france’ uit te breiden.Je kunt ook andere statistieken toevoegen die de percentages en cumulatieve percentages van elk type frequentie laten zien, zodat je beter kunt inschatten hoeveel woorden het grootste deel van het totaal vormen, als dat al zo is:
Met meer data valt er meer te analyseren, en kom je doorgaans op meer verrassingen uit.
Hoe ziet dit er uit in een realistische setting met echte data?
Je bekijkt filmtitels, ziet welke woorden het meest in de titels voorkomen — dat is de absolute frequentie — en welke woorden geassocieerd zijn met de meeste omzet, oftewel de gewogen frequentie.
Boxoffice Mojo heeft een lijst van meer dan 15.000 films, samen met hun bijbehorende boxoffice-omzet en rangen. Begin met het scrapen van de data met
requestsenBeautifulSoup— Je kunt Boxoffice Mojo hier alvast verkennen als je wilt:Je ziet dat sommige numerieke waarden speciale tekens bevatten (
$,,en^), en dat sommige waarden eigenlijkN/Azijn. Dus die moet je aanpassen:Nu zet je de lijst om in een pandas-
DataFramedoor de kolommen de juiste namen te geven en te converteren naar de datatypes die je wilt.Het woord ‘star’ is een van de toppers, omdat het in vijf van de top vijftien films voorkomt, en je weet ook dat de Star Wars-serie nog meer films heeft, waarvan er meerdere ook in de top staan.
Laten we nu de code die je ontwikkelde gebruiken en kijken hoe die werkt op deze dataset. Er is niets nieuws in de onderstaande code; je zet het simpelweg in één functie:
Niet verrassend staan de ‘stopwoorden’ bovenaan, wat bij de meeste collecties documenten het geval is. Je hebt ze ook dubbel, waarbij sommige met een hoofdletter beginnen en andere niet. Er zijn dus twee duidelijke dingen om aan te pakken:
Hier is een eenvoudige update van de functie (nieuwe parameter
rm_words, evenals regels 6, 7 en 8):Bekijk hetzelfde DataFrame eens gesorteerd op
abs_freq:Laten we visualiseren om beide te vergelijken en de verborgen trends te zien:
Het lijkt er in elk geval in de hoofden van producenten en schrijvers op dat liefde alles overwint! Het is het meest gebruikte woord in alle filmtitels. Het staat echter niet zo hoog als het gaat om gewogen frequentie (boxoffice-omzet).
Met andere woorden: als je naar alle filmtitels kijkt, is ‘love’ het woord dat je het vaakst tegenkomt. Maar als je schat welk woord het meest verscheen in de ogen van de kijkers (waarbij boxoffice-omzet als metric wordt gebruikt), dan zouden ‘2’, ‘star’ en ‘man’ het meest bekeken zijn, of geassocieerd met de meeste omzet.
Om het duidelijk te zeggen: dit zijn heel eenvoudige berekeningen. Als je zegt dat de gewogen frequentie van het woord ‘love’ 1.604.106.767 is, betekent dat simpelweg dat de som van de lifetime boxoffice van alle films waarvan de titel het woord ‘love’ bevatte, dat bedrag was.
Het is ook interessant dat ‘2’ het topwoord is. Het is natuurlijk geen woord, maar het is een indicatie dat tweede delen van filmseries een zeer groot bedrag vertegenwoordigen. Dat geldt ook voor ‘3’, dat op de vijfde plaats staat. Merk op dat ‘part’ en ‘ii’ ook in de top tien staan, wat hetzelfde bevestigt.
‘American’ en ‘movie’ hebben een hoge relatieve waarde.
Een korte noot over de gebruikte stopwoorden in deze functie
Gewoonlijk zou je een uitgebreidere lijst met stopwoorden gebruiken dan hier, zeker als je met artikelen of socialmediaberichten werkt. Zo biedt het pakket
nltklijsten met stopwoorden in meerdere talen, die je kunt downloaden en gebruiken.De woorden hier zijn gekozen na een paar checks op de topfilms. Veel daarvan worden meestal als stopwoorden beschouwd, maar in het geval van filmtitels was het logisch om sommige te behouden omdat ze inzicht kunnen geven. Zo kunnen de woorden ‘I’, ‘me’, ‘you’ op sociale dynamiek wijzen. Een andere reden is dat filmtitels erg korte zinnen zijn, en we daaruit zo veel mogelijk betekenis proberen te halen.
Je kunt dit zeker met je eigen set woorden proberen en licht andere resultaten zien.
Als we terugkijken naar de oorspronkelijke lijst met filmtitels, zien we dat sommige van de topwoorden niet eens in de top tien voorkomen, en dit is precies het soort inzicht dat we proberen te ontdekken met deze aanpak.
Vervolgens is het logisch om verder te kijken naar de topwoorden die interessant zijn. Laten we de films filteren die ‘2’ bevatten en kijken:
Bekijk ook even de top-‘star’-films:
En tot slot de top-‘man’-films:
Volgende stappen en verbeteringen
Als eerste stap kun je proberen meer woorden te verzamelen: filmtitels zijn extreem kort en brengen vaak niet de letterlijke betekenis van de woorden over. Zo is een peetvader eigenlijk iemand die getuige is van de doop van een kind en belooft voor dat kind te zorgen (of misschien een maffioso die voor zijn plezier moordt?!).
Een volgende oefening kan zijn om meer gedetailleerde beschrijvingen te verzamelen, naast de filmtitel. Bijvoorbeeld:
vertelt ons veel meer over de film dan ‘The Matrix’.
Of je kunt een van de onderstaande paden nemen om je analyse te verrijken:
Misschien wil je de data zelf verkennen, evenals andere data:
Oké, je hebt nu de tellingen van woorden in filmtitels verkend, en het verschil gezien tussen absolute en gewogen frequenties, en hoe het weglaten van een van beide een groot deel van het beeld kan missen. Je hebt ook de beperkingen van deze aanpak gezien, en suggesties om je analyse te verbeteren.
Je hebt het proces doorlopen van het maken van een speciale functie die je eenvoudig kunt draaien om elke vergelijkbare tekstdataset met getallen te analyseren, en je weet hoe dit je begrip van dit soort datasets kan verbeteren.
Probeer het door de prestaties van je tweets te analyseren, de URL’s van je website, je Facebook-berichten, of een andere vergelijkbare dataset die je tegenkomt.
Het is misschien makkelijker om gewoon de repository te clonen met de code en het zelf te proberen.
De functie
word_frequencymaakt deel uit van het pakket advertools, dat je kunt downloaden en gebruiken in je werk/onderzoek.Check het en laat me weten wat je vindt! @eliasdabbas
Bekijk DataCamp’s Python Dictionaries-tutorial om te leren hoe je woordfrequentie maakt, en de tutorial Web Scraping & NLP in Python om te leren hoe je verdelingen van woordfrequenties plot.