Saltar al contenido principal

Frecuencia absoluta y ponderada de palabras en texto

En este tutorial, aprenderás qué son la frecuencia absoluta y la frecuencia ponderada en minería de texto y cómo calcularlas con defaultdict y DataFrames de pandas.
· 15 min leer

Explora con IA

Un conjunto importante de métricas en la minería de texto tiene que ver con la frecuencia de las palabras (o de cualquier token) en un determinado corpus de documentos. No obstante, también puedes usar un conjunto adicional de métricas cuando cada documento tiene asociado un valor numérico que describe algún atributo del propio documento.

Algunos ejemplos:

  • Tuits y su número de interacciones.
  • URLs y sus páginas vistas y rebotes.
  • Títulos de películas y su recaudación bruta.
  • Palabras clave y sus impresiones, clics y conversiones.

En este tutorial,

  • Primero verás cómo crear una función sencilla que calcule y compare la ocurrencia absoluta y la ponderada de palabras en un corpus de documentos. A veces esto destapa tendencias y agregados ocultos que no se aprecian mirando solo el top diez. A menudo, además, difieren de la frecuencia absoluta.
  • Después, trabajarás con un conjunto de datos real (títulos de películas y su recaudación bruta) para intentar descubrir tendencias ocultas. Un adelanto: el amor aparecerá por algún lado.
  • Usarás Python como lenguaje de programación y te apoyarás en la estructura de datos defaultdict del módulo collections para el trabajo pesado, además de DataFrames de pandas para gestionar el resultado final.

Frecuencia absoluta y ponderada de palabras: introducción

Supón que tienes dos tuits y que su contenido y número de impresiones (vistas) son los siguientes:

Texto del tuitVistas
spain800
france200

Es fácil hacer el análisis básico y ver que tus palabras se reparten 50:50 entre “france” y “spain”. En muchos casos, esto es todo lo que tienes: puedes medir solo la frecuencia absoluta de las palabras e intentar inferir ciertas relaciones. En este caso, además, dispones de un dato numérico para cada documento.

La frecuencia ponderada aquí es claramente distinta: el reparto es 80:20. En otras palabras, aunque “spain” y “france” aparecen una vez cada una en tus tuits, desde la perspectiva de tus lectores la primera apareció 800 veces y la segunda 200. ¡Hay una gran diferencia!

Frecuencia simple de palabras con defaultdict

Ahora considera este ejemplo un poco más elaborado para un conjunto similar de documentos:

DocumentoVistas
france200
spain180
spain beaches170
france beaches160
spain best beaches160

Ahora iteras por los documentos, los separas en palabras y cuentas las apariciones de cada una:

 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

En el bucle anterior, la primera línea recorre uno a uno los elementos de text_list. La segunda línea (dentro de cada documento) recorre las palabras de cada elemento, separadas por el espacio (podría ser cualquier otro carácter: “-”, “,”, “_”, etc.).

Al intentar asignar un valor a word_freq[word] hay dos escenarios posibles:

  1. La clave word existe: se realiza la asignación (sumando uno).
  2. La clave word no está en word_freq: en ese caso defaultdict llama a la función por defecto con la que se definió, que aquí es int.

Cuando se llama a int, devuelve cero. Ahora la clave existe, su valor es cero y ya puede incrementarse en 1.

Aunque en la primera tabla la palabra destacada era “france”, tras contar todas las palabras dentro de cada documento vemos que “spain” y “beaches” empatan en la primera posición. Esto es clave para destapar tendencias ocultas, sobre todo cuando trabajas con listas de documentos de decenas o cientos de miles.

Frecuencia ponderada de palabras

Ahora que ya has contado las apariciones de cada palabra en el corpus, quieres ver la frecuencia ponderada. Es decir, quieres saber cuántas veces vieron tus lectores esas palabras en comparación con cuántas veces las usaste.

En la primera tabla, la frecuencia absoluta se repartía equitativamente entre “spain” y “france”, pero “spain” tenía claramente más peso, porque su valor era 800, frente a 200 de “france”.

Pero ¿cuál sería la frecuencia ponderada para la segunda tabla, un poco más compleja?

¡Vamos a verlo!

Puedes reutilizar parte del código anterior, con algunas adiciones:

 # el valor por defecto ahora es una lista con dos enteros
word_freq = defaultdict(lambda: [0, 0]) 

# la columna `views` que tenías en el primer DataFrame
num_list = [200, 180, 170, 160, 160]

# ahora iteramos por texto y números a la vez
for text, num in zip(text_list, num_list): 
    for word in text.split(): 
        # igual que antes
        word_freq[word][0] += 1 
        # línea nueva: incrementa el valor numérico para cada palabra
        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

Algunas observaciones:

  • Aunque “france” era la frase más alta en conjunto, “spain” y “beaches” ganan protagonismo al considerar la frecuencia ponderada.
  • rel_value es una simple división para obtener el valor por aparición de cada palabra.
  • Viendo rel_value, también notas que, aunque “france” es bastante baja en wtd_freq, tiene potencial porque el valor por aparición es alto. Esto podría sugerir, por ejemplo, ampliar la cobertura de contenido sobre “france”.

También puedes añadir otras métricas que muestren los porcentajes y los porcentajes acumulados de cada tipo de frecuencia para tener una mejor perspectiva de cuántas palabras concentran la mayor parte del total, si es el caso:

 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

Con más datos, hay mucho más que analizar y suelen aparecer sorpresas.

¿Cómo se ve esto en un caso real con datos reales?

Vamos a examinar títulos de películas, ver qué palabras se usan más en los títulos —frecuencia absoluta— y cuáles se asocian con mayor recaudación —frecuencia ponderada—.

Boxoffice Mojo tiene una lista con más de 15.000 películas junto con su recaudación bruta y su ranking. Empieza extrayendo los datos con requests y BeautifulSoup. Puedes explorar Boxoffice Mojo aquí si quieres:

 
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')
    # prueba y error para dar con las posiciones exactas
    table_data = [x.text for x in soup.select('tr td')[11:511]]  
    # agrupa cada 5 valores en una fila
    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
67BV$620,181,3822017
78WB$534,858,4442008^
89BV$532,177,3242016
910BV$504,014,1652017
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

Verás que algunos valores numéricos incluyen caracteres especiales ($, , y ^), y algunos valores son N/A. Hay que limpiarlos:

na_year_idx =  [i for i, x in enumerate(final_list) if x[4] == 'n/a'] # índices de los valores 'n/a'
new_years = [1998, 1999, 1960, 1973]  # obtenidos comprobando 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

Ahora convierte la lista en un DataFrame de pandas, nombrando las columnas adecuadamente y convirtiendo a los tipos de datos deseados.

 import re
regex = '|'.join(['\$', ',', '\^'])

columns = ['rank', 'title', 'studio', 'lifetime_gross', 'year']

boxoffice_df = pd.DataFrame({
    'rank': [int(x[0]) for x in final_list],  # convierte los rangos a enteros
    'title': [x[1] for x in final_list],  # títulos tal cual
    'studio': [x[2] for x in final_list],  # estudios tal cual
    'lifetime_gross': [int(re.sub(regex, '', x[3])) for x in final_list],  # elimina caracteres especiales y convierte a entero
    'year': [int(re.sub(regex, '', str(x[4]))) for x in final_list],  # elimina caracteres especiales y convierte a entero
})
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

La palabra “star” está entre las más frecuentes: aparece en cinco de las quince primeras películas, y la saga Star Wars incluye más títulos, varios de ellos también en el top.

Ahora vamos a usar el código que has desarrollado para ver cómo funciona con este conjunto de datos. No hay nada nuevo en el código siguiente: solo lo empaquetamos en una función:

 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

Como era de esperar, las “stop words” dominan el ranking, algo común en la mayoría de colecciones de documentos. Además, aparecen duplicadas por mayúsculas y minúsculas. Hay dos cosas claras que resolver:

  1. Eliminar todas las stop words: puedes hacerlo añadiendo un nuevo parámetro a la función y pasando tu propia lista de stop words.
  2. Normalizar a minúsculas para evitar duplicados.

Aquí tienes una actualización simple de la función (nuevo parámetro rm_words, y líneas 6, 7 y 8):

# la lista de palabras se ampliará
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): 
            # Ignora la palabra si está en la lista de stop words
            if word.lower() in rm_words:  
                continue                  
            # .lower() evita duplicar palabras
            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

Veamos el mismo DataFrame ordenado por 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

Ahora vamos a visualizar para comparar ambas vistas y descubrir tendencias ocultas:

 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='Número de veces que la palabra aparece en un título de película; de un total de 15500 películas.', fontsize=14)
plt.text(0.4, -1.8, s='Datos: boxofficemojo.com Abr. 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('Palabras más usadas en títulos de películas', 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='Recaudación histórica total de todas las películas cuyo título contiene la palabra. (La palabra superior es "2") ', fontsize=14)
plt.text(0.4, -1.8, s='Recogida de datos y metodología: 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('Palabras más asociadas a la recaudación de taquilla', fontsize=22, fontweight='bold')
plt.tight_layout(pad=0.01)
plt.show()

words most box office data

Parece que, al menos en la mente de productores y guionistas, ¡el amor todo lo puede! Es la palabra más utilizada en los títulos. Sin embargo, no está tan arriba si hablamos de frecuencia ponderada (recaudación).

En otras palabras, si miras todos los títulos, “love” es la palabra que más te encontrarás. Pero si estimas qué palabra “vieron” más los espectadores (usando la recaudación bruta como métrica), entonces “2”, “star” y “man” serían las más vistas, o las asociadas a mayor recaudación.

Para aclararlo: son cálculos muy simples. Que la frecuencia ponderada de “love” sea 1.604.106.767 significa simplemente que la suma de la recaudación histórica de todas las películas cuyo título incluye “love” es esa cantidad.

También es curioso que “2” lidere. Obviamente no es una palabra, pero indica que las segundas partes de las sagas suman muchísimo. Lo mismo con “3”, en quinta posición. Observa que “part” e “ii” también están en el top ten, reforzando la misma idea.

“American” y “movie” tienen un valor relativo alto.

Una nota rápida sobre las stop words usadas en esta función

Normalmente usarías una lista más completa de stop words que la de aquí, especialmente si trabajas con artículos o publicaciones en redes sociales. Por ejemplo, el paquete nltk ofrece listas de stop words en varios idiomas que puedes descargar y usar.

Las palabras aquí se eligieron tras revisar algunas de las películas top. Muchas suelen considerarse stop words, pero en títulos de películas tiene sentido mantener algunas porque pueden aportar información. Por ejemplo, “I”, “me”, “you” pueden sugerir dinámicas sociales. Además, los títulos son frases muy cortas e intentamos extraer el máximo sentido posible.

Prueba con tu propio conjunto de palabras y verás resultados algo distintos.

Si vuelves a la lista original de títulos, verás que algunas palabras destacadas ni siquiera aparecen en el top diez, y eso es justo el tipo de insight que buscamos con este enfoque.

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

Después, tiene sentido explorar con más detalle las palabras más interesantes. Filtramos las películas que contienen “2”:

(boxoffice_df[boxoffice_df['title']                  
              .str
              .contains('2 | 2', case=False)] # espacios para excluir términos como '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

Echemos también un vistazo a las películas con “star”:

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

Y, por último, las películas con “man”:

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

Próximos pasos y mejoras

Como primer paso, podrías intentar obtener más palabras: los títulos de películas son muy cortos y a menudo no transmiten el significado literal. Por ejemplo, un “padrino” debería ser quien presencia el bautizo de un niño y promete cuidarlo (¿o quizá un mafioso que mata por placer?).

Un ejercicio adicional sería obtener descripciones más detalladas, además del título. Por ejemplo:

"A computer hacker learns from mysterious rebels about the true nature of his reality and his role in the war against its controllers."

nos cuenta mucho más de la película que “The Matrix”.

Alternativamente, también puedes seguir alguno de estos caminos para enriquecer el análisis:

  • Mejor análisis estadístico: tratamiento de valores extremos/outliers y uso de otras métricas.
  • Minería de texto: agrupar palabras y temas similares (“happy”, “happiness”, “happily”, etc.).
  • Análisis granular: ejecutar la misma función por años/décadas o por estudios de producción concretos.

Quizá te interese explorar tú mismo estos datos y otros:

Ya has explorado los conteos de palabras en títulos de películas y visto la diferencia entre frecuencias absoluta y ponderada, y cómo omitir una puede dejar fuera parte importante de la foto. También has visto las limitaciones de este enfoque y algunas ideas para mejorar el análisis.

Has creado una función específica que puedes ejecutar fácilmente para analizar cualquier conjunto de datos de texto con números y entender mejor este tipo de datos.

Pruébalo con el rendimiento de tus tuits, las URLs de tu web, tus publicaciones en Facebook o cualquier conjunto de datos similar que encuentres.

Quizá te resulte más cómodo clonar el repositorio con el código y probarlo tú mismo.

La función word_frequency forma parte del paquete advertools, que puedes descargar y usar en tu trabajo o investigación.

Échale un vistazo y cuéntame qué tal: @eliasdabbas

Echa un ojo al tutorial de diccionarios en Python de DataCamp para aprender a crear frecuencias de palabras, y al tutorial de web scraping y PLN en Python para aprender a representar distribuciones de frecuencias.

Temas

Relacionado