Tần suất Tuyệt đối và Có trọng số của Từ trong Văn bản
Trong hướng dẫn này, bạn sẽ tìm hiểu về tần suất từ tuyệt đối và có trọng số trong khai phá văn bản cũng như cách tính bằng defaultdict và pandas DataFrames.
Một nhóm chỉ số quan trọng trong khai phá văn bản liên quan đến tần suất xuất hiện của từ (hoặc bất kỳ token nào) trong một tập hợp tài liệu văn bản nhất định. Tuy nhiên, bạn cũng có thể dùng thêm một nhóm chỉ số khác trong các trường hợp mà mỗi tài liệu có kèm theo một giá trị số mô tả thuộc tính nào đó của tài liệu.
Một vài ví dụ:
Tweet và số lượt tương tác tương ứng.
URL cùng lượt xem trang và số lần thoát.
Tựa phim và doanh thu phòng vé.
Từ khóa cùng số lần hiển thị, lượt nhấp và chuyển đổi.
Trong hướng dẫn này,
Trước tiên, bạn sẽ đi qua quy trình tạo một hàm đơn giản để tính và so sánh số lần xuất hiện tuyệt đối và có trọng số của các từ trong một tập tài liệu. Cách này đôi khi giúp lộ ra các xu hướng ẩn và số liệu tổng hợp mà không nhất thiết thể hiện rõ khi chỉ nhìn vào khoảng mười giá trị đứng đầu. Chúng cũng thường khác với tần suất từ tuyệt đối.
Sau đó, bạn sẽ xem một tập dữ liệu thực tế (tựa phim và doanh thu phòng vé), và hy vọng khám phá các xu hướng ẩn. Gợi mở: kiểu gì cũng có “tình yêu” xuất hiện!
Bạn sẽ dùng Python làm ngôn ngữ lập trình và sử dụng cấu trúc dữ liệu defaultdict của mô-đun collections cho phần xử lý chính, cũng như DataFrame của pandas để quản lý đầu ra cuối cùng.
Tần suất từ Tuyệt đối và Có trọng số: Giới thiệu
Giả sử bạn có hai tweet và nội dung cùng số lần hiển thị (lượt xem) của chúng như sau:
Nội dung Tweet
Lượt xem
spain
800
france
200
Phân tích cơ bản khá đơn giản: các từ của bạn chia đều 50:50 giữa 'france' và 'spain'. Trong nhiều trường hợp, đó là tất cả những gì bạn có, và bạn chỉ có thể đo tần suất từ tuyệt đối rồi cố suy ra một số mối quan hệ. Ở đây, bạn lại có thêm dữ liệu về từng tài liệu.
Tần suất có trọng số lúc này rõ ràng khác, với tỷ lệ 80:20. Nói cách khác, dù 'spain' và 'france' đều xuất hiện một lần trong tweet của bạn, nhưng từ góc nhìn của người đọc, từ đầu xuất hiện 800 lần, còn từ sau xuất hiện 200 lần. Khác biệt là rất lớn!
Tần suất từ đơn giản với defaultdict
Bây giờ hãy xét một ví dụ phức tạp hơn đôi chút với tập tài liệu tương tự:
Tài liệu
Lượt xem
france
200
spain
180
spain beaches
170
france beaches
160
spain best beaches
160
Bạn sẽ lặp qua các tài liệu, tách chúng thành từ, và đếm số lần xuất hiện của từng từ:
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'})Được hỗ trợ bởi
abs_freq
spain
3
beaches
3
france
2
best
1
Trong vòng lặp trên, dòng đầu tiên lặp qua text_list từng phần tử một. Dòng thứ hai (trong phạm vi từng tài liệu) lặp qua các từ của từng phần tử, được tách theo ký tự khoảng trắng (cũng có thể là ký tự khác ('-', ',', '_', v.v.)).
Khi bạn cố gán giá trị cho word_freq[word] sẽ có hai kịch bản:
Khóa word đã tồn tại: khi đó phép gán được thực hiện (cộng thêm một)
Khóa word chưa có trong word_freq, khi đó defaultdict sẽ gọi hàm mặc định đã gán khi định nghĩa, ở đây là int.
Khi gọi int nó trả về 0. Lúc này khóa đã tồn tại, giá trị của nó là 0 và sẵn sàng được cộng thêm 1.
Dù từ đứng đầu là 'france' trong bảng đầu tiên, sau khi đếm tất cả từ trong từng tài liệu, ta thấy 'spain' và 'beaches' đồng hạng nhất. Điều này quan trọng để phát hiện các xu hướng ẩn, đặc biệt khi danh sách tài liệu bạn xử lý lên tới hàng chục hay hàng trăm nghìn.
Tần suất từ có trọng số
Sau khi bạn đã đếm số lần xuất hiện của mỗi từ trong toàn bộ tập tài liệu, bạn muốn xem tần suất có trọng số. Tức là, bạn muốn biết các từ xuất hiện trong mắt người đọc bao nhiêu lần, so với số lần bạn sử dụng chúng.
Trong bảng đầu tiên, tần suất tuyệt đối của các từ chia đều giữa 'spain' và 'france', nhưng 'spain' rõ ràng nặng ký hơn, vì giá trị của nó là 800, so với 200 của 'france'.
Vậy tần suất từ có trọng số cho bảng thứ hai, phức tạp hơn một chút, sẽ như thế nào?
Hãy cùng tìm hiểu!
Bạn có thể tái sử dụng một phần mã ở trên, nhưng thêm vài điểm:
# 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'])Được hỗ trợ bởi
abs_freq
wtd_freq
rel_value
spain
3
510
170
beaches
3
490
163
france
2
360
180
best
1
160
160
Một vài nhận xét:
Dù 'france' là cụm từ cao nhất tổng thể, 'spain' và 'beaches' lại nổi bật hơn khi xét tần suất có trọng số.
rel_value là phép chia đơn giản để lấy giá trị trên mỗi lần xuất hiện của từng từ.
Nhìn vào rel_value, bạn cũng thấy rằng, dù 'france' khá thấp theo chỉ số wtd_freq, nó vẫn có tiềm năng vì giá trị trên mỗi lần xuất hiện cao. Điều này có thể gợi ý tăng mức độ phủ nội dung về 'france', chẳng hạn.
Bạn cũng có thể bổ sung vài chỉ số khác thể hiện tỷ lệ phần trăm và tích lũy theo từng loại tần suất, để có góc nhìn tốt hơn về số lượng từ tạo nên phần lớn tổng giá trị, nếu có:
Bạn có thể phân tích sâu hơn nữa, và với nhiều dữ liệu hơn bạn thường sẽ có thêm nhiều bất ngờ.
Vậy điều này trông như thế nào trong bối cảnh thực với dữ liệu thực?
Bạn sẽ xem các tựa phim, xem từ nào được dùng nhiều nhất trong tựa — đó là tần suất tuyệt đối — và từ nào gắn với doanh thu nhiều nhất, tức tần suất có trọng số.
Boxoffice Mojo có danh sách hơn 15.000 bộ phim cùng doanh thu phòng vé và thứ hạng. Hãy bắt đầu bằng cách thu thập dữ liệu bằng requests và BeautifulSoup — Bạn có thể khám phá Boxoffice Mojo tại đây trước nếu muốn:
import requests
from bs4 import BeautifulSoupĐược hỗ trợ bởi
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)Được hỗ trợ bởi
Bạn sẽ thấy một số giá trị số có ký tự đặc biệt ($, , và ^), và một số giá trị thực ra là N/A. Vậy nên bạn cần chuyển đổi chúng:
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)Được hỗ trợ bởi
(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]Được hỗ trợ bởi
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)Được hỗ trợ bởi
['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Được hỗ trợ bởi
Bây giờ bạn chuyển danh sách thành DataFrame của pandas bằng cách đặt tên cột phù hợp và chuyển sang kiểu dữ liệu mong muốn.
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)Được hỗ trợ bởi
rows:15500columns:5
data types:
lifetime_gross int64
rank int64
studio object
title object
year int64
dtype: object
Được hỗ trợ bởi
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
Từ 'star' là một trong những từ hàng đầu, vì xuất hiện trong năm trên mười lăm phim top, và bạn cũng biết loạt Star Wars còn nhiều phim nữa, vài phim trong số đó cũng thuộc top.
Giờ hãy tận dụng đoạn mã bạn đã phát triển và xem cách nó hoạt động trên tập dữ liệu này. Không có gì mới trong mã dưới đây, bạn chỉ gom lại vào một hàm:
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()Được hỗ trợ bởi
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
Không có gì ngạc nhiên, “từ dừng” đứng đầu, điều này khá giống với hầu hết tập hợp tài liệu. Bạn cũng có bản trùng, nơi vài từ viết hoa và vài từ không. Vậy bạn có hai việc rõ ràng cần xử lý:
Loại bỏ tất cả từ dừng: bạn có thể làm bằng cách thêm tham số mới cho hàm, và cung cấp danh sách từ dừng của riêng bạn.
Chuẩn hóa tất cả từ về chữ thường để loại trùng lặp
Dưới đây là cập nhật đơn giản cho hàm (tham số mới rm_words, cũng như các dòng 6, 7 và 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_dfĐược hỗ trợ bởi
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Được hỗ trợ bởi
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
Hãy xem cùng DataFrame đó nhưng sắp xếp theo abs_freq:
(word_freq_df.sort_values('abs_freq', ascending=False).head(15).style.bar(['abs_freq','wtd_freq','rel_value'],
color='#60DDFF'))Được hỗ trợ bởi
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
Bây giờ hãy trực quan hóa để so sánh cả hai và thấy các xu hướng ẩn:
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()Được hỗ trợ bởi
Có vẻ như ít nhất là trong suy nghĩ của nhà sản xuất và biên kịch, tình yêu quả thật chiến thắng tất cả! Đây là từ được dùng nhiều nhất trong toàn bộ tựa phim. Tuy nhiên, khi xét tần suất có trọng số (doanh thu phòng vé), nó không cao đến vậy.
Nói cách khác, nếu bạn xem tất cả tựa phim, 'love' sẽ là từ bạn bắt gặp nhiều nhất. Nhưng nếu ước lượng từ nào xuất hiện nhiều nhất trong mắt khán giả (dùng doanh thu phòng vé làm thước đo), thì '2', 'star' và 'man' sẽ là những từ được xem nhiều nhất, hay gắn với nhiều doanh thu nhất.
Xin lưu ý: đây là các phép tính rất đơn giản. Khi nói tần suất có trọng số của từ 'love' là 1.604.106.767, điều đó đơn giản nghĩa là tổng doanh thu trọn đời của tất cả phim có tựa chứa từ 'love' bằng con số đó.
Cũng thú vị khi '2' là từ đứng đầu. Rõ ràng nó không phải là một từ, nhưng nó cho thấy phần hai của các loạt phim đóng góp tổng doanh thu rất lớn. '3' cũng vậy, đứng vị trí thứ năm. Lưu ý 'part' và 'ii' cũng nằm trong top 10, củng cố cho nhận định này.
'American' và 'movie' có giá trị tương đối cao.
Lưu ý nhanh về danh sách từ dừng dùng trong hàm này
Thông thường, bạn sẽ cung cấp một danh sách từ dừng đầy đủ hơn danh sách ở đây, đặc biệt nếu bạn xử lý bài viết hay bài đăng mạng xã hội. Ví dụ gói nltk cung cấp danh sách từ dừng ở nhiều ngôn ngữ, có thể tải về và dùng.
Các từ ở đây được chọn sau vài lần kiểm tra trên các phim top. Nhiều từ trong đó thường được xem là từ dừng, nhưng trong trường hợp tựa phim, hợp lý khi giữ lại một số từ vì có thể đem lại chút thông tin. Chẳng hạn, các từ 'I', 'me', 'you' có thể gợi mở về động lực xã hội. Một lý do khác là tựa phim thường là cụm rất ngắn, và chúng ta cố gắng rút ra càng nhiều ý nghĩa càng tốt.
Bạn hoàn toàn có thể thử với tập từ của riêng mình và sẽ thấy kết quả hơi khác.
Quay lại danh sách tựa phim gốc, ta thấy một số từ hàng đầu thậm chí không xuất hiện trong top mười, và đó chính xác là kiểu insight mà chúng ta cố gắng khám phá bằng cách tiếp cận này.
Tiếp theo, có lẽ hợp lý khi khám phá sâu hơn các từ hàng đầu thú vị. Hãy lọc các phim có chứa '2' và xem:
(boxoffice_df[boxoffice_df['title'].str.contains('2 | 2',case=False)]# spaces used to exclude words like '2010'.head(10))Được hỗ trợ bởi
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
Hãy xem qua các phim 'star' đứng đầu:
boxoffice_df[boxoffice_df['title'].str.contains('star | star',case=False)].head(10)Được hỗ trợ bởi
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
Và cuối cùng, các phim 'man' đứng đầu:
boxoffice_df[boxoffice_df['title'].str.contains('man | man',case=False)].head(10)Được hỗ trợ bởi
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
Bước tiếp theo và cải tiến
Bước đầu tiên, bạn có thể thử lấy thêm từ: tựa phim cực kỳ ngắn và nhiều khi không chuyển tải nghĩa đen của từ. Ví dụ, một “godfather” vốn là người chứng kiến lễ rửa tội của một đứa trẻ, và hứa sẽ chăm sóc đứa trẻ đó (hoặc có khi là một tay mafia giết người vì thú vui?!).
Bài tập tiếp theo có thể là lấy mô tả chi tiết hơn, ngoài tựa phim. Ví dụ:
"Một hacker máy tính được các chiến binh bí ẩn tiết lộ về bản chất thực sự của thực tại và vai trò của anh ta trong cuộc chiến chống lại những kẻ kiểm soát nó."
cho chúng ta biết nhiều hơn về bộ phim, so với 'The Matrix'.
Ngoài ra, bạn cũng có thể đi theo một trong các hướng dưới đây để làm giàu phân tích:
Phân tích thống kê tốt hơn: xử lý giá trị cực đoan/ngoại lai, dùng các chỉ số khác.
Khai phá văn bản: nhóm các từ và chủ đề tương tự lại với nhau ('happy', 'happiness', 'happily', v.v.).
Phân tích chi tiết hơn: chạy cùng hàm cho các năm/thập kỷ khác nhau, hoặc cho một số hãng sản xuất nhất định.
Bạn có thể muốn tự mình khám phá dữ liệu này cũng như dữ liệu khác:
Vậy là bạn đã khám phá số lần xuất hiện của từ trong tựa phim, và thấy sự khác nhau giữa tần suất tuyệt đối và có trọng số, cũng như việc bỏ qua một trong hai có thể khiến bạn bỏ lỡ một phần lớn bức tranh. Bạn cũng đã thấy giới hạn của cách tiếp cận này, và có vài gợi ý để cải thiện phân tích.
Bạn đã đi qua quy trình tạo một hàm chuyên biệt mà bạn có thể dễ dàng chạy để phân tích bất kỳ tập dữ liệu văn bản tương tự nào có kèm số liệu, và biết cách điều đó giúp cải thiện hiểu biết của bạn về loại dữ liệu này.
Hãy thử phân tích hiệu quả tweet của bạn, các URL trên website, bài đăng Facebook, hoặc bất kỳ tập dữ liệu tương tự nào bạn gặp.
Một nhóm chỉ số quan trọng trong khai phá văn bản liên quan đến tần suất xuất hiện của từ (hoặc bất kỳ token nào) trong một tập hợp tài liệu văn bản nhất định. Tuy nhiên, bạn cũng có thể dùng thêm một nhóm chỉ số khác trong các trường hợp mà mỗi tài liệu có kèm theo một giá trị số mô tả thuộc tính nào đó của tài liệu.
Một vài ví dụ:
Trong hướng dẫn này,
defaultdictcủa mô-đuncollectionscho phần xử lý chính, cũng nhưDataFramecủa pandas để quản lý đầu ra cuối cùng.Tần suất từ Tuyệt đối và Có trọng số: Giới thiệu
Giả sử bạn có hai tweet và nội dung cùng số lần hiển thị (lượt xem) của chúng như sau:
Phân tích cơ bản khá đơn giản: các từ của bạn chia đều 50:50 giữa 'france' và 'spain'. Trong nhiều trường hợp, đó là tất cả những gì bạn có, và bạn chỉ có thể đo tần suất từ tuyệt đối rồi cố suy ra một số mối quan hệ. Ở đây, bạn lại có thêm dữ liệu về từng tài liệu.
Tần suất có trọng số lúc này rõ ràng khác, với tỷ lệ 80:20. Nói cách khác, dù 'spain' và 'france' đều xuất hiện một lần trong tweet của bạn, nhưng từ góc nhìn của người đọc, từ đầu xuất hiện 800 lần, còn từ sau xuất hiện 200 lần. Khác biệt là rất lớn!
Tần suất từ đơn giản với
defaultdictBây giờ hãy xét một ví dụ phức tạp hơn đôi chút với tập tài liệu tương tự:
Bạn sẽ lặp qua các tài liệu, tách chúng thành từ, và đếm số lần xuất hiện của từng từ:
Trong vòng lặp trên, dòng đầu tiên lặp qua
text_listtừng phần tử một. Dòng thứ hai (trong phạm vi từng tài liệu) lặp qua các từ của từng phần tử, được tách theo ký tự khoảng trắng (cũng có thể là ký tự khác ('-', ',', '_', v.v.)).Khi bạn cố gán giá trị cho
word_freq[word]sẽ có hai kịch bản:wordđã tồn tại: khi đó phép gán được thực hiện (cộng thêm một)wordchưa có trongword_freq, khi đódefaultdictsẽ gọi hàm mặc định đã gán khi định nghĩa, ở đây làint.Khi gọi
intnó trả về 0. Lúc này khóa đã tồn tại, giá trị của nó là 0 và sẵn sàng được cộng thêm 1.Dù từ đứng đầu là 'france' trong bảng đầu tiên, sau khi đếm tất cả từ trong từng tài liệu, ta thấy 'spain' và 'beaches' đồng hạng nhất. Điều này quan trọng để phát hiện các xu hướng ẩn, đặc biệt khi danh sách tài liệu bạn xử lý lên tới hàng chục hay hàng trăm nghìn.
Tần suất từ có trọng số
Sau khi bạn đã đếm số lần xuất hiện của mỗi từ trong toàn bộ tập tài liệu, bạn muốn xem tần suất có trọng số. Tức là, bạn muốn biết các từ xuất hiện trong mắt người đọc bao nhiêu lần, so với số lần bạn sử dụng chúng.
Trong bảng đầu tiên, tần suất tuyệt đối của các từ chia đều giữa 'spain' và 'france', nhưng 'spain' rõ ràng nặng ký hơn, vì giá trị của nó là 800, so với 200 của 'france'.
Vậy tần suất từ có trọng số cho bảng thứ hai, phức tạp hơn một chút, sẽ như thế nào?
Hãy cùng tìm hiểu!
Bạn có thể tái sử dụng một phần mã ở trên, nhưng thêm vài điểm:
Một vài nhận xét:
rel_valuelà phép chia đơn giản để lấy giá trị trên mỗi lần xuất hiện của từng từ.rel_value, bạn cũng thấy rằng, dù 'france' khá thấp theo chỉ sốwtd_freq, nó vẫn có tiềm năng vì giá trị trên mỗi lần xuất hiện cao. Điều này có thể gợi ý tăng mức độ phủ nội dung về 'france', chẳng hạn.Bạn cũng có thể bổ sung vài chỉ số khác thể hiện tỷ lệ phần trăm và tích lũy theo từng loại tần suất, để có góc nhìn tốt hơn về số lượng từ tạo nên phần lớn tổng giá trị, nếu có:
Bạn có thể phân tích sâu hơn nữa, và với nhiều dữ liệu hơn bạn thường sẽ có thêm nhiều bất ngờ.
Vậy điều này trông như thế nào trong bối cảnh thực với dữ liệu thực?
Bạn sẽ xem các tựa phim, xem từ nào được dùng nhiều nhất trong tựa — đó là tần suất tuyệt đối — và từ nào gắn với doanh thu nhiều nhất, tức tần suất có trọng số.
Boxoffice Mojo có danh sách hơn 15.000 bộ phim cùng doanh thu phòng vé và thứ hạng. Hãy bắt đầu bằng cách thu thập dữ liệu bằng
requestsvàBeautifulSoup— Bạn có thể khám phá Boxoffice Mojo tại đây trước nếu muốn:Bạn sẽ thấy một số giá trị số có ký tự đặc biệt (
$,,và^), và một số giá trị thực ra làN/A. Vậy nên bạn cần chuyển đổi chúng:Bây giờ bạn chuyển danh sách thành
DataFramecủa pandas bằng cách đặt tên cột phù hợp và chuyển sang kiểu dữ liệu mong muốn.Từ 'star' là một trong những từ hàng đầu, vì xuất hiện trong năm trên mười lăm phim top, và bạn cũng biết loạt Star Wars còn nhiều phim nữa, vài phim trong số đó cũng thuộc top.
Giờ hãy tận dụng đoạn mã bạn đã phát triển và xem cách nó hoạt động trên tập dữ liệu này. Không có gì mới trong mã dưới đây, bạn chỉ gom lại vào một hàm:
Không có gì ngạc nhiên, “từ dừng” đứng đầu, điều này khá giống với hầu hết tập hợp tài liệu. Bạn cũng có bản trùng, nơi vài từ viết hoa và vài từ không. Vậy bạn có hai việc rõ ràng cần xử lý:
Dưới đây là cập nhật đơn giản cho hàm (tham số mới
rm_words, cũng như các dòng 6, 7 và 8):Hãy xem cùng DataFrame đó nhưng sắp xếp theo
abs_freq:Bây giờ hãy trực quan hóa để so sánh cả hai và thấy các xu hướng ẩn:
Có vẻ như ít nhất là trong suy nghĩ của nhà sản xuất và biên kịch, tình yêu quả thật chiến thắng tất cả! Đây là từ được dùng nhiều nhất trong toàn bộ tựa phim. Tuy nhiên, khi xét tần suất có trọng số (doanh thu phòng vé), nó không cao đến vậy.
Nói cách khác, nếu bạn xem tất cả tựa phim, 'love' sẽ là từ bạn bắt gặp nhiều nhất. Nhưng nếu ước lượng từ nào xuất hiện nhiều nhất trong mắt khán giả (dùng doanh thu phòng vé làm thước đo), thì '2', 'star' và 'man' sẽ là những từ được xem nhiều nhất, hay gắn với nhiều doanh thu nhất.
Xin lưu ý: đây là các phép tính rất đơn giản. Khi nói tần suất có trọng số của từ 'love' là 1.604.106.767, điều đó đơn giản nghĩa là tổng doanh thu trọn đời của tất cả phim có tựa chứa từ 'love' bằng con số đó.
Cũng thú vị khi '2' là từ đứng đầu. Rõ ràng nó không phải là một từ, nhưng nó cho thấy phần hai của các loạt phim đóng góp tổng doanh thu rất lớn. '3' cũng vậy, đứng vị trí thứ năm. Lưu ý 'part' và 'ii' cũng nằm trong top 10, củng cố cho nhận định này.
'American' và 'movie' có giá trị tương đối cao.
Lưu ý nhanh về danh sách từ dừng dùng trong hàm này
Thông thường, bạn sẽ cung cấp một danh sách từ dừng đầy đủ hơn danh sách ở đây, đặc biệt nếu bạn xử lý bài viết hay bài đăng mạng xã hội. Ví dụ gói
nltkcung cấp danh sách từ dừng ở nhiều ngôn ngữ, có thể tải về và dùng.Các từ ở đây được chọn sau vài lần kiểm tra trên các phim top. Nhiều từ trong đó thường được xem là từ dừng, nhưng trong trường hợp tựa phim, hợp lý khi giữ lại một số từ vì có thể đem lại chút thông tin. Chẳng hạn, các từ 'I', 'me', 'you' có thể gợi mở về động lực xã hội. Một lý do khác là tựa phim thường là cụm rất ngắn, và chúng ta cố gắng rút ra càng nhiều ý nghĩa càng tốt.
Bạn hoàn toàn có thể thử với tập từ của riêng mình và sẽ thấy kết quả hơi khác.
Quay lại danh sách tựa phim gốc, ta thấy một số từ hàng đầu thậm chí không xuất hiện trong top mười, và đó chính xác là kiểu insight mà chúng ta cố gắng khám phá bằng cách tiếp cận này.
Tiếp theo, có lẽ hợp lý khi khám phá sâu hơn các từ hàng đầu thú vị. Hãy lọc các phim có chứa '2' và xem:
Hãy xem qua các phim 'star' đứng đầu:
Và cuối cùng, các phim 'man' đứng đầu:
Bước tiếp theo và cải tiến
Bước đầu tiên, bạn có thể thử lấy thêm từ: tựa phim cực kỳ ngắn và nhiều khi không chuyển tải nghĩa đen của từ. Ví dụ, một “godfather” vốn là người chứng kiến lễ rửa tội của một đứa trẻ, và hứa sẽ chăm sóc đứa trẻ đó (hoặc có khi là một tay mafia giết người vì thú vui?!).
Bài tập tiếp theo có thể là lấy mô tả chi tiết hơn, ngoài tựa phim. Ví dụ:
cho chúng ta biết nhiều hơn về bộ phim, so với 'The Matrix'.
Ngoài ra, bạn cũng có thể đi theo một trong các hướng dưới đây để làm giàu phân tích:
Bạn có thể muốn tự mình khám phá dữ liệu này cũng như dữ liệu khác:
Vậy là bạn đã khám phá số lần xuất hiện của từ trong tựa phim, và thấy sự khác nhau giữa tần suất tuyệt đối và có trọng số, cũng như việc bỏ qua một trong hai có thể khiến bạn bỏ lỡ một phần lớn bức tranh. Bạn cũng đã thấy giới hạn của cách tiếp cận này, và có vài gợi ý để cải thiện phân tích.
Bạn đã đi qua quy trình tạo một hàm chuyên biệt mà bạn có thể dễ dàng chạy để phân tích bất kỳ tập dữ liệu văn bản tương tự nào có kèm số liệu, và biết cách điều đó giúp cải thiện hiểu biết của bạn về loại dữ liệu này.
Hãy thử phân tích hiệu quả tweet của bạn, các URL trên website, bài đăng Facebook, hoặc bất kỳ tập dữ liệu tương tự nào bạn gặp.
Sẽ dễ hơn nếu bạn chỉ cần clone kho lưu trữ với mã nguồn và tự thử.
Hàm
word_frequencylà một phần của gói advertools, bạn có thể tải về và dùng trong công việc/nghiên cứu.Hãy xem thử và cho tôi biết! @eliasdabbas
Hãy xem Hướng dẫn Từ điển Python của DataCamp để học cách tạo tần suất từ, và hướng dẫn Web Scraping & NLP trong Python để học cách vẽ phân phối tần suất từ.