Ga naar hoofdinhoud

Absolute en gewogen frequentie van woorden in tekst

In deze tutorial leer je over absolute en gewogen woordfrequentie in text mining en hoe je die berekent met defaultdict en pandas DataFrames.
· 15 min lezen

Verken met AI

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-tekstViews
spain800
france200

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:

DocumentViews
france200
spain180
spain beaches170
france beaches160
spain best beaches160

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'})
 abs_freq
spain3
beaches3
france2
best1

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:

  1. De sleutel word bestaat: in dat geval wordt de toekenning gedaan (er wordt één bij opgeteld)
  2. 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 numbers
for text, num in zip(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'])
 abs_freqwtd_freqrel_value
spain3510170
beaches3490163
france2360180
best1160160

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)
 abs_freqabs_percabs_perc_cumwtd_freqwtd_freq_percwtd_freq_perc_cumrel_value
spain30.3333330.3333335100.3355260.335526170
beaches30.3333330.6666674900.3223680.657895163
france20.2222220.8888893600.2368420.894737180
best10.11111111600.1052631160

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:

 
import requests
from bs4 import BeautifulSoup
 final_list = []

for i in range(1, 156):
    if not 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 in range(0, len(table_data[:-4]), 5)] 
    for temp in temp_list:
        final_list.append(temp)
10
20
30
40
50
60
70
80
90
100
110
120
130
140
150
boxoffice_df = pd.DataFrame.from_records(final_list)

boxoffice_df.head(10)
 01234
01Star Wars: The Force AwakensBV$936,662,2252015
12AvatarFox$760,507,6252009^
23Black PantherBV$681,084,1092018
34TitanicPar.$659,363,9441997^
45Jurassic WorldUni.$652,270,6252015
56Marvel's The AvengersBV$623,357,9102012
67BVStar Wars: The Last Jedi2017
78WBThe Dark Knight2008^
89BVRogue One: A Star Wars Story2016
910BVBeauty and the Beast (2017)2017
boxoffice_df.tail(15)
 01234
1548515486The Dark HoursN/A$4232005
15486154872:22Magn.$4222017
1548715488State ParkAtl$4211988
1548815489The Magician (2010)Reg.$4062010
1548915490SkinlessPPF$4002014
1549015491CinemanovelsMont.$3982014
1549115492Hannah: Buddhism's Untold JourneyKL$3962016
1549215493Apartment 143Magn.$3832012
1549315494The MarshAll.$3362007
1549415495The ChambermaidFM$3152015
1549515496News From Planet MarsKL$3102016
1549615497Trojan WarWB$3091997
1549715498Lou! Journal infimeDistrib.$2872015
1549815499InterventionAll.$2792007
1549915500PlaybackMagn.$2642012

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 in enumerate(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 online

print(*[(i, x) for i, x in enumerate(final_list) if i in na_year_idx], sep='\n')
print('new year values:', new_years)
(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'])
new year values: [1998, 1999, 1960, 1973]
for na_year, new_year in zip(na_year_idx, new_years):
    final_list[na_year][4] = new_year
    print(final_list[na_year], new_year)
['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] 1973

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)
rows: 15500
columns: 5

data types:
lifetime_gross     int64
rank               int64
studio            object
title             object
year               int64
dtype: object
 lifetime_grossrankstudiotitleyear
09366622251BVStar Wars: The Force Awakens2015
17605076252FoxAvatar2009
26810841093BVBlack Panther2018
36593639444Par.Titanic1997
46522706255Uni.Jurassic World2015
56233579106BVMarvel's The Avengers2012
66201813827BVStar Wars: The Last Jedi2017
75348584448WBThe Dark Knight2008
85321773249BVRogue One: A Star Wars Story2016
950401416510BVBeauty and the Beast (2017)2017
1048629556111BVFinding Dory2016
1147454467712FoxStar Wars: Episode I - The Phantom Menace1999
1246099800713FoxStar Wars1977
1345900586814BVAvengers: Age of Ultron2015
1444813909915WBThe Dark Knight Rises2012

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:

 def word_frequency(text_list, num_list, sep=None):
    word_freq = defaultdict(lambda: [0, 0])

    for text, num in zip(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()
 abs_freqabs_percabs_perc_cumwtd_freqwtd_freq_percwtd_freq_perc_cumrel_value
The30550.0687470.068747675183424980.0811670.08116722100930.0
the13100.0294790.098227329731008600.0396390.12080625170306.0
of13990.0314820.129709301805924670.0362820.15708721572975.0
and5450.0122640.141973121881138470.0146520.17173922363512.0
21580.0035560.14552990326730580.0108590.18259857168817.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:

  1. 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.
  2. 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 expanded
def word_frequency(text_list, num_list, sep=None, rm_words=('the', 'and', 'a')):  
    word_freq = defaultdict(lambda: [0, 0])

    for text, num in zip(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 words
            if 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_df
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') # E6E9EB

 wordabs_freqabs_percabs_perc_cumwtd_freqwtd_freq_percwtd_freq_perc_cumrel_value
021580.004439450.0044394590326730580.01376340.01376345.71688e+07
1star450.00126440.0057038553746588190.008189530.02195291.19437e+08
2man1950.005479070.011182939670378540.00604470.02799762.03438e+07
3part410.001152010.012334932625797770.004971290.03296897.95751e+07
4movie1170.003287440.015622432160505570.004900390.03786932.74876e+07
53630.001770160.017392531975911930.004872270.04274155.07554e+07
6ii670.001882550.019275130777128830.00468960.04743114.5936e+07
7wars:60.0001685870.019443727574971550.004201680.05163284.59583e+08
8last1330.0037370.023180726702296510.004068710.05570152.00769e+07
9harry270.0007586426113297140.003978960.05968059.67159e+07
10me1400.003933690.02787324593301280.003747360.06342791.75666e+07
11potter100.0002809780.02815423948114270.003649050.06707692.39481e+08
12black710.001994940.030148923723064670.003614760.07069173.34128e+07
13-490.001376790.031525723394848780.003564740.07425644.77446e+07
14story1070.003006460.034532222314375260.003400110.07765652.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'))

 wordabs_freqabs_percabs_perc_cumwtd_freqwtd_freq_percwtd_freq_perc_cumrel_value
26love2110.005928630.06954216042068850.002444380.1113997.60288e+06
2man1950.005479070.011182939670378540.00604470.02799762.03438e+07
24my1910.005366680.061899416295404980.002482980.1064788.53163e+06
15i1680.004720430.039252622034397860.003357450.0810141.31157e+07
021580.004439450.0044394590326730580.01376340.01376345.71688e+07
10me1400.003933690.02787324593301280.003747360.06342791.75666e+07
31life1340.00376510.076482215346477320.002338390.1232971.14526e+07
8last1330.0037370.023180726702296510.004068710.05570152.00769e+07
23you1260.003540320.056532717137588530.002611310.1039951.36013e+07
4movie1170.003287440.015622432160505570.004900390.03786932.74876e+07
14story1070.003006460.034532222314375260.003400110.07765652.08546e+07
33night1040.002922170.081455515239173600.002322040.1279461.46531e+07
19american930.002613090.047850518688541920.002847630.09325912.00952e+07
117girl900.00252880.1516448427716610.001284160.266639.36413e+06
16day870.0024445121641987600.003297660.08431162.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 in enumerate(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 in enumerate(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()

woorden meest box office data

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.

boxoffice_df.head(10)
 lifetime_grossrankstudiotitleyear
09366622251BVStar Wars: The Force Awakens2015
17605076252FoxAvatar2009
26810841093BVBlack Panther2018
36593639444Par.Titanic1997
46522706255Uni.Jurassic World2015
56233579106BVMarvel's The Avengers2012
66201813827BVStar Wars: The Last Jedi2017
75348584448WBThe Dark Knight2008
85321773249BVRogue One: A Star Wars Story2016
950401416510BVBeauty and the Beast (2017)2017

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))
 lifetime_grossrankstudiotitleyear
1544122624716DWShrek 22004
3038981310131BVGuardians of the Galaxy Vol. 22017
3138101121932WBHarry Potter and the Deathly Hallows Part 22011
3537358582536SonySpider-Man 22004
3836806126539Uni.Despicable Me 22013
6431243333165Par.Iron Man 22010
7929232473780LG/SThe Twilight Saga: Breaking Dawn Part 22012
8728172390288LGFThe Hunger Games: Mockingjay - Part 22015
117245852179118BVToy Story 21999
146226164286147NLRush Hour 22001

Bekijk ook even de top-‘star’-films:

boxoffice_df[boxoffice_df['title'].str.contains('star | star', case=False)].head(10)
 lifetime_grossrankstudiotitleyear
09366622251BVStar Wars: The Force Awakens2015
66201813827BVStar Wars: The Last Jedi2017
85321773249BVRogue One: A Star Wars Story2016
1147454467712FoxStar Wars: Episode I - The Phantom Menace1999
1246099800713FoxStar Wars1977
3338027057734FoxStar Wars: Episode III - Revenge of the Sith2005
6531067674066FoxStar Wars: Episode II - Attack of the Clones2002
105257730019106Par.Star Trek2009
140228778661141Par.Star Trek Into Darkness2013
301158848340302Par.Star Trek Beyond2016

En tot slot de top-‘man’-films:

boxoffice_df[boxoffice_df['title'].str.contains('man | man', case=False)].head(10)
 lifetime_grossrankstudiotitleyear
1842331581219BVPirates of the Caribbean: Dead Man's Chest2006
2240901399423BVIron Man 32013
3537358582536SonySpider-Man 22004
4833653030349SonySpider-Man 32007
5333036019454WBBatman v Superman: Dawn of Justice2016
5931841210160Par.Iron Man2008
6431243333165Par.Iron Man 22010
8229104551883WBMan of Steel2013
176206852432177WBBatman Begins2005
182202853933183SonyThe Amazing Spider-Man 22014

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

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.

Onderwerpen

Gerelateerd