Python 全栈 60 天精通之路

Day 40:Pandas 实战 Kaggle 百万级影评数据集之 10 大问题探索分析

发布日期:2022年3月12日 18:02 阅读: 178 访问: 179

今天继续探索 Twitter 电影数据集,昨天我们已经对这个数据集完成特征工程处理,三张表分别关于电影、用户和电影评分。在探索前,我们设想几个有趣的问题,循着好奇心,更容易坚持下去,看完今天这篇 EDA 实战。问题包括:29 类电影中,猜测下哪几类影片数是最多的?从上世纪初到现在,电影的产出数是平稳的还是线型增长,或者指数增长?</li

今天继续探索 Twitter 电影数据集,昨天我们已经对这个数据集完成特征工程处理,三张表分别关于电影、用户和电影评分。在探索前,我们设想几个有趣的问题,循着好奇心,更容易坚持下去,看完今天这篇 EDA 实战。

问题包括:

  1. 29 类电影中,猜测下哪几类影片数是最多的?
  2. 从上世纪初到现在,电影的产出数是平稳的还是线型增长,或者指数增长?
  3. 喜剧片、动作片、爱情片、惊悚片你心目中的 TOP10 榜单是怎样的?根据 Twitter 80 多万影评,挖出的 TOP 榜单又是怎样的?
  4. 近 100 年,所有电影的 TOP10 榜单里有我们熟知的肖申克救赎,阿甘正传吗?
  5. 近 100 年,最烂的垃圾篇 BAD10 榜单里都有哪些部电影被不幸入选?
  6. 哪些电影是最有槽点的,被人们茶余饭后津津乐道呢?
  7. 有哪些时期人们的吐槽兴致大增?
  8. 哪些影迷最能吐槽?他们的 Twitter ID 也被挖出来了!
  9. 他们的吐槽数能有几千条吗?
  10. 他们的评论严厉吗?平均评论得分是多少?

今天,通过 Twitter 电影数据集进行有趣的数据分析,一一回答以上 8 个问题。

今天课程的完整 notebook 代码,下载地址链接:

https://pan.baidu.com/s/1oR1Ok4_1gfY9M4Q_RzRu4w

提取码:ryp6

今天课程使用的包如下:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pyecharts.charts import Bar,Grid,Line,Pie
import pyecharts.options as opts
from pyecharts.globals import ThemeType

from snapshot_phantomjs import snapshot # pyecharts 导出图片使用到的库
from pyecharts.render import make_snapshot

 

哪几类影片数最多?

重新熟悉下三个 DataFrame 的前五行:

movies2.head()

 

users.head()

 

ratings.head()

 

昨天已经得出最百花齐放的前 10 类电影:

top10

 

[('Mystery', 2649),
 ('Adventure', 3116),
 ('Documentary', 3224),
 ('Horror', 4288),
 ('Crime', 4723),
 ('Action', 5175),
 ('Romance', 5987),
 ('Thriller', 7307),
 ('Comedy', 10741),
 ('Drama', 17589)]

 

排名第一的是 Drama(戏剧)类电影,一共有 17589 部,那么它的出厂时间跨度呢?

movies2.drop('index',axis=1,inplace=True) # 删除 index 列
movies2

 

mdrama = movies2[movies2['Genre'].str.contains('Drama')]
mdrama

mdrama.sort_values(by='year') 

 

看到 Drama 电影最早出厂追溯到上世纪 1909 年, 最近出厂年份 2019 年,整整110年。

近百年电影的产出数如何变化?

接下来,我们每十年,对数据完成下采样。

tmp = mdrama.copy()
tmp.loc[:,'yeardt'] = pd.to_datetime(mdrama['year'],format='%Y')
tmp
tmpdt_index = pd.DatetimeIndex(tmp['yeardt'].dt.date)
tmpdt_index # 先创建 DatetimeIndex 索引对象

tmp.loc[:,'Movie Count'] = 1
tmp10 =  tmp.set_index(tmpdt_index).resample('10Y')['Movie Count'].sum().to_frame()
tmp10

 

对 resample 我们在专栏《Day 36:Pandas 与数据读取、选取、清洗、特征工程相关的 12 个实用小功能》里已经讲到,不会的读者可以返回去看看。

最终得到的结果如下所示:

直观展示以上数据:

c = (
    Bar()
    .add_xaxis(tmp10.index.year.to_list())
    .add_yaxis("电影数", tmp10['Movie Count'].to_list(), category_gap=0,color='blue')
   .set_global_opts(title_opts=opts.TitleOpts(title="每10年Drama类电影生产数"))

)

c.render_notebook()

 

折线图:

x_data = list(map(str,tmp10.index.year.to_list()))
y_data = tmp10['Movie Count'].to_list()
l0 = (
    Line()
    .add_xaxis(xaxis_data=x_data)
    .add_yaxis(
        series_name="",
        y_axis=y_data,
        symbol="emptyCircle",
        areastyle_opts=opts.AreaStyleOpts(opacity=1, color="#C67570")
    )

)

l0.render_notebook()

 

将上面对 Drama 类电影的分析过程,整理为一个函数,分别分析其他 9 类电影的产量变化情况。

### 整理以上脚本,分析其他前 10 种类的电影

def mgenre_ana(mg):
    print(mg)
    mdrama = movies2[movies2['Genre'].str.contains(mg)]
    #mdrama
    mdrama.sort_values(by='year') 
    tmp = mdrama.copy()
    tmp.loc[:,'yeardt'] = pd.to_datetime(mdrama['year'],format='%Y')
    #tmp
    tmpdt_index = pd.DatetimeIndex(tmp['yeardt'].dt.date)
    tmpdt_index # 先创建 DatetimeIndex 索引对象
    tmp.loc[:,'Movie Count'] = 1
    tmp10 =  tmp.set_index(tmpdt_index).resample('10Y')['Movie Count'].sum().to_frame()
    #tmp10
    c = (
    Bar()
    .add_xaxis(tmp10.index.year.to_list())
    .add_yaxis("电影数", tmp10['Movie Count'].to_list(), category_gap=0,color='blue')
   .set_global_opts(title_opts=opts.TitleOpts(title="每10年%s电影生产数"%(mg,)))
    )

    x_data = list(map(str,tmp10.index.year.to_list()))
    y_data = tmp10['Movie Count'].to_list()
    l = (
    Line()
    .add_xaxis(xaxis_data=x_data)
    .add_yaxis(
        series_name="",
        y_axis=y_data,
        symbol="emptyCircle",
        areastyle_opts=opts.AreaStyleOpts(opacity=1, color="#C67570")
      )
    )
    return [c,l]

 

运行这个 notebook 前,请自行下载安装文件 PhantomJS,下载地址:

https://phantomjs.org/download.html

下载后一定要放置在与此 Jupyter Notebook 同一个安装路径下。

top10_genre_names = [x for x,y in top10]
for g in top10_genre_names:
    c,l = mgenre_ana(g)
    make_snapshot(snapshot, c.render(), "%s.png"%(g,))
    make_snapshot(snapshot, l.render(), "%sline.png"%(g,))

 

其他 9 类电影每 10 年的电影产量。

Action 类:

Adventure 类:

Comedy 类:

Crime 类:

Documentary 类:

Horror 类:

Mystery 类:

Romance 类:

Thriller 类:

每类电影的 TOP 榜单

代码如下,看到 Pandas 的可读性很强,没有一个 for 循环。

# 先连接 movies2 和 ratings 两个 DataFrame
def rating_top10(mg,topn=10):
    print(mg)
    mdrama = movies2[movies2['Genre'].str.contains(mg)]
    mmerge = mdrama.merge(ratings,on='Movie ID')
    mcount = mmerge['Movie ID'].value_counts()
    mcount2 = mcount.sort_values()
    #mcount2
    mask = mcount2 > 100
    # 看到有的影片只有 1 次被评论,显然这种平均分求前 topn 的可信度不高,我们设置一个电影被评论次数超过 100 次的 topn 电影
    tmp = mmerge.set_index('Movie ID')# index 对齐
    mmerge2 = tmp[mask]
    mpivot = mmerge2.pivot_table(columns='Movie ID',values=['Rating'],aggfunc=[np.mean])
    #mpivot
    mmelt = mpivot['mean'].melt(value_name ='Rating') # 宽表变为长表,符合我们的习惯
    #mmelt
    mtop10 = mmelt.sort_values('Rating')[-topn:] # 返回得分最高的前 topn 部电影
    return mtop10.merge(movies,on='Movie ID', how='left') # 连接表得到完整的电影得分前 topn 电影信息

 

调用函数:

top10_genre_names = [x for x,y in top10]
for g in top10_genre_names:
    topn = 50
    result = rating_top10(g,topn)
    result
    result.to_csv('./score_topn/%s-top%d.csv'%(g,topn),encoding='utf-8')

 

得到 10 个 CSV 文件,保存着 TOP50 的榜单。

Action 类电影的 TOP10 榜单:

Movie IDRatingMovie TitleGenre
4685699.280967The Dark Knight (2008)Action|Crime|Drama|Thriller
1030649.103004Terminator 2: Judgment Day (1991)Action|Sci-Fi
41547969.038913Avengers: Endgame (2019)Action|Adventure|Fantasy|Sci-Fi
13756669.012629Inception (2010)Action|Adventure|Sci-Fi|Thriller
38635529.01005Bajrangi Bhaijaan (2015)Action|Comedy|Drama
50743528.982196Dangal (2016)Action|Biography|Drama|Sport
1724958.855072Gladiator (2000)Action|Adventure|Drama
1104138.790083L茅on (1994)Action|Crime|Drama|Thriller
41547568.764341Avengers: Infinity War (2018)Action|Adventure|Sci-Fi

Comedy 类:

Movie IDRatingMovie TitleGenre
55128729.985836Be Somebody (2016)Comedy|Drama|Romance
38635529.01005Bajrangi Bhaijaan (2015)Action|Comedy|Drama
887638.945946Back to the Future (1985)Adventure|Comedy|Sci-Fi
1187998.862573La vita bella (1997)Comedy|Drama|Romance|War
16754348.851211The Intouchables (2011)Biography|Comedy|Drama
707358.839286The Sting (1973)Comedy|Crime|Drama
451528.824427Singin' in the Rain (1952)Comedy|Musical|Romance
23803078.740077Coco (2017)Animation|Adventure|Comedy|Family|Fantasy|Music|Mystery
4357618.73262Toy Story 3 (2010)Animation|Adventure|Comedy|Family|Fantasy

更多榜单就不再放到文章里了,欢迎手动执行 notebook 脚本,获得 10 个榜单 CSV 文件。

10 部最佳影片

整个 3 万多部影片中,分析出评分最佳的 10 部影片。

# 先连接 movies2 和 ratings 两个 DataFrame
def all_rating_top10(topn=10,reversed=True):
    mdrama = movies2
    mmerge = mdrama.merge(ratings,on='Movie ID')
    mcount = mmerge['Movie ID'].value_counts()
    mcount2 = mcount.sort_values()
    #mcount2
    mask = mcount2 > 100
    # 看到有的影片只有 1 次被评论,显然这种平均分求前 topn 的可信度不高,我们设置一个电影被评论次数超过 100 次的 topn 电影
    tmp = mmerge.set_index('Movie ID')# index 对齐
    mmerge2 = tmp[mask]
    mpivot = mmerge2.pivot_table(columns='Movie ID',values=['Rating'],aggfunc=[np.mean])
    #mpivot
    mmelt = mpivot['mean'].melt(value_name ='Rating') # 宽表变为长表,符合我们的习惯
    #mmelt
    if reversed is True:
        mtop10 = mmelt.sort_values('Rating')[-topn:] # 返回得分最高的前 topn 部电影
        return mtop10.merge(movies,on='Movie ID', how='left') # 连接表得到完整的电影得分前 topn 电影信息

    mbad10 = mmelt.sort_values('Rating')[:topn]
    return mbad10.merge(movies,on='Movie ID', how='left') # 连接表得到完整的电影得分前 topn 电影信息

 

调用:

TOP10 = all_rating_top10()
TOP10

 

TOP10c = TOP10.sort_values(by='Rating',ascending=False) #这样看着更习惯
TOP10c

 

绘制柱状图:

x = TOP10c['Movie Title'].to_list()
y = TOP10c['Rating'].round(2).to_list()
bar = (
    Bar()
    .add_xaxis(x)
    .add_yaxis('得分',y,category_gap='50%')
    .reversal_axis()
    .set_global_opts(title_opts=opts.TitleOpts(title="电影得分TOP榜单"),
                    xaxis_opts=opts.AxisOpts(min_=8.0,name='得分'),
                    toolbox_opts=opts.ToolboxOpts(),)
)
grid = (
    Grid(init_opts=opts.InitOpts(theme=ThemeType.DARK))#MACARONS
    .add(bar, grid_opts=opts.GridOpts(pos_left="30%"))
)
grid.render_notebook()

 

10 部最垃圾的影片

BAD10 = all_rating_top10(reversed=False) # 人类历史最垃圾的 10 部电影
BAD10

 

绘制柱状图:

x = BAD10['Movie Title'].to_list()
y = BAD10['Rating'].round(2).to_list()
bar = (
    Bar()
    .add_xaxis(x)
    .add_yaxis('得分',y,category_gap='50%')
    .reversal_axis()
    .set_global_opts(title_opts=opts.TitleOpts(title="电影得分BAD榜单"),
                    xaxis_opts=opts.AxisOpts(min_=3.0,name='得分'),
                    toolbox_opts=opts.ToolboxOpts(),)
)
grid = (
    Grid(init_opts=opts.InitOpts(theme=ThemeType.DARK))#MACARONS
    .add(bar, grid_opts=opts.GridOpts(pos_left="30%"))
)
grid.render_notebook()

 

电影被吐槽数 TOP 榜单

mmerge = movies2.merge(ratings,on='Movie ID')
mcount = mmerge['Movie ID'].value_counts()
mcount3 = mcount.sort_values()
mcount4 = mcount3[-10:].reset_index()
mcount4.columns=['Movie ID','Rating Count']
mcount5 = mcount4.merge(movies,on='Movie ID', how='left')
mcount5

 

绘制柱状图:

x = mcount5['Movie Title'].to_list()
y = mcount5['Rating Count'].round(2).to_list()
bar = (
    Bar()
    .add_xaxis(x)
    .add_yaxis('吐槽数',y,category_gap='50%')
    .reversal_axis()
    .set_global_opts(title_opts=opts.TitleOpts(title="电影被吐槽TOP榜单"),
                    xaxis_opts=opts.AxisOpts(min_=8.0,name='吐槽数'),
                    toolbox_opts=opts.ToolboxOpts(),)
)
grid = (
    Grid(init_opts=opts.InitOpts(theme=ThemeType.DARK))
    .add(bar, grid_opts=opts.GridOpts(pos_left="30%"))
)
grid.render_notebook()

 

电影被吐槽最多 TOP 月份

ratings.loc[:,'ym'] = ratings['rating_dt'].map(lambda x: 100*x.year + x.month)
ratings
ratingmonth = ratings['ym'].value_counts()
rating_as_month = ratingmonth.sort_values()[-10:]
rating_as_month

 

结果:

201311    15872
201306    15989
201307    16341
201501    16501
201310    16656
201312    16897
201303    17089
201308    18652
201401    21209
201309    22635
Name: ym, dtype: int64

 

绘制柱状图:

x = rating_as_month.index.to_list()
y = rating_as_month.to_list()
bar = (
    Bar()
    .add_xaxis(x)
    .add_yaxis('年月',y,category_gap='50%')
    .reversal_axis()
    .set_global_opts(title_opts=opts.TitleOpts(title="电影被吐槽数"),
                    xaxis_opts=opts.AxisOpts(min_=8.0,name='吐槽数'),
                    toolbox_opts=opts.ToolboxOpts(),)
)
grid = (
    Grid(init_opts=opts.InitOpts(theme=ThemeType.DARK))
    .add(bar, grid_opts=opts.GridOpts(pos_left="30%"))
)
grid.render_notebook()

 

最能吐槽的影迷 TOP 榜单

user_ratings = ratings['User ID'].value_counts()
user_ratings_sort = user_ratings.sort_values()
user_ratings2 = user_ratings_sort.reset_index()
user_ratings2.columns = ['User ID', 'Rating Count']
user_ratings2[-10:].merge(users,on='User ID',how='left')

 

评论平均得分

user_pivot = ratings.pivot_table(columns='User ID',values=['Rating'],aggfunc=[np.mean])
user_pivot2 = user_pivot['mean']
user_pivot2
user_melt = user_pivot2.melt(value_name='Rating Score')
mask5 = user_melt['User ID'].isin(user_ratings2[-10:]['User ID'])
user_melt2 = user_melt[mask5].sort_values(by='Rating Score')
user_melt2.merge(user_ratings2[-10:],on='User ID',how='left')

 

小结

今天的知识点有很多个,涉及很多数据分析常用的经典的方法,强烈建议大家动手执行下今天的 notebook,体会里面常用的方法:

  • merge
  • pivot_table
  • melt
  • sort_values
  • value_counts
  • Series.map
  • isin
  • drop_duplicates
  • set_index
  • reset_index
  • pyecharts 绘图常用方法