电影小数据分析

一、预处理

先给源码和数据集
git:https://github.com/linkunxin/filmRvenuePred
百度网盘:https://pan.baidu.com/s/1_rpzLeTFf14x6KoxwWY4Lw

1、数据初步处理

先来看一下数据集样子
在这里插入图片描述
然后总览一下数据情况( info() ),在此之前,我们往往将测试集和训练集先合并,统一处理后再分开,就不用处理两遍了。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
train ='train.csv'
test ='test.csv'
dataTrain =pd.read_csv(train)
dataTest =pd.read_csv(test)
data =pd.merge(dataTrain,dataTest,how='outer')       #合并测试训练集
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 7398 entries, 0 to 7397
Data columns (total 23 columns):
id                       7398 non-null int64
belongs_to_collection    1481 non-null object
budget                   7398 non-null int64
genres                   7375 non-null object
homepage                 2366 non-null object
imdb_id                  7398 non-null object
original_language        7398 non-null object
original_title           7398 non-null object
overview                 7376 non-null object
popularity               7398 non-null float64
poster_path              7396 non-null object
production_companies     6984 non-null object
production_countries     7241 non-null object
release_date             7397 non-null object
runtime                  7392 non-null float64
spoken_languages         7336 non-null object
status                   7396 non-null object
tagline                  5938 non-null object
title                    7395 non-null object
Keywords                 6729 non-null object
cast                     7372 non-null object
crew                     7360 non-null object
revenue                  3000 non-null float64
dtypes: float64(3), int64(2), object(18)
memory usage: 1.4+ MB
  • belongs_to_collection和homepage出现大量空值,数据集中将不采用

  • imdb_id可以用ID来表示、poster_path暂时没必要

data.status.value_counts()
Released           7385
Rumored               6
Post Production       5
Name: status, dtype: int64
  • status状态信息有三种值,其中released占绝大部分,所以特征并不明显,所以弃掉
cnt =data[data.budget==0].count()['budget']
print(cnt,cnt/len(data))
2023 0.2734522844011895
  • budget有2023个0值,缺失占比近百分之30,暂时不作采用。

2、缺失值处理

  • 采用平均值补充:
data.loc[col0.isnull(),col] = col0.mean() #用平均值来补缺失值
  • 用到的离散型数据基本没有缺失值,有则返回空列表,下面将作介绍

3、异常值处理

1)数值型

revenue
print(np.sort(data.revenue)[:50])
np.array(data.budget[np.argsort(data.revenue)[:50].values])
[  1.   1.   1.   1.   2.   3.   3.   3.   4.   5.   6.   7.   8.   8.
   8.  10.  10.  11.  12.  12.  13.  15.  18.  18.  20.  23.  25.  25.
  25.  30.  30.  32.  46.  60.  70.  79.  85.  88.  97. 100. 100. 121.
 125. 126. 135. 198. 204. 241. 306. 311.]





array([      12,        2,      592,        0,        1,        1,
         750000,        0,      344,        1,  6400000,        0,
              6,      130,        0,        0,        0,        0,
       23000000, 16000000,        0,        0, 10000000,        0,
              0, 12000000,        0,        4,        0,  2000000,
              0,        0,  9000000,        0,        0,        0,
              5,        0,        0,        1,   410000,        0,
              0,        0,        5,   500000,        0,        0,
              0,        0], dtype=int64)
  • 第一个输出是revenue的排序后的最小的50名

  • 第二个输出的是与其对应的budget,也就是预计开支。
    从常识来考虑,这两个的关系是挺大的,budget中除了一些0值,其他的budget还是蛮高的,但再看回第一个输出,他们的revenue这么低是不填合理的,所以这肯定就是有不少的异常值,可能是revenue记录时打少了N个0等原因造成的。

  • 所以我们需对其进行异常值处理

本框架采用两种方法查找异常值:
  • 1、3sigma原则。
  • 2、箱型图识别异常值。
dealAbnormal(self,col,method)	#	method:3sigma或box
发现3sigma效果好点(最后拟合的效果)
popularity
print(np.sort(data.popularity)[:50])
print(np.sort(data.popularity)[7350:])
pd.DataFrame({'a':list(data.popularity)}).boxplot()
data.popularity.describe()
[1.0000e-06 1.0000e-06 3.0800e-04 4.6400e-04 5.7800e-04 6.5700e-04
 8.4400e-04 1.2230e-03 1.2720e-03 1.3930e-03 1.8800e-03 2.2290e-03
 2.3540e-03 2.8170e-03 3.0130e-03 3.3050e-03 3.5680e-03 3.6260e-03
 3.6470e-03 4.4250e-03 4.7060e-03 5.3510e-03 7.2940e-03 7.8290e-03
 7.8770e-03 9.0570e-03 9.9610e-03 1.1427e-02 1.1574e-02 1.6219e-02
 2.1580e-02 2.2347e-02 3.0576e-02 3.7986e-02 3.8560e-02 3.8876e-02
 3.9793e-02 4.2036e-02 4.3519e-02 4.4048e-02 4.4561e-02 4.5860e-02
 4.7875e-02 4.7908e-02 5.6270e-02 5.7737e-02 6.0645e-02 6.3442e-02
 6.3568e-02 6.4310e-02]
[ 40.796775  41.048867  41.051421  41.109264  41.225769  41.613762
  41.725123  42.061481  42.149697  42.965027  43.847654  44.251369
  45.38298   47.326665  48.307194  48.573287  49.247505  50.903593
  51.645403  52.854103  53.291601  54.581997  63.869599  64.29999
  68.726676  72.884078  75.385211  76.93789   88.439243  88.561239
  89.887648  96.272374 123.167259 133.82782  140.950236 145.882135
 146.161786 147.098006 154.801009 183.870374 185.070892 185.330992
 187.860492 213.849907 228.032744 287.253654 294.337037 547.488298]
 
count    7398.000000
mean        8.514968
std        12.165794
min         0.000001
25%         3.933124
50%         7.435844
75%        10.920002
max       547.488298
Name: popularity, dtype: float64

[外链图片转存失败(img-qyNbOVfl-1564565542008)(output_19_2.png)]

  • 再来看看数值型的声望值(popularity)

  • 我们看到极差不小,最关键的是平均值和最大值的差距,非常大。Q3也只是10而已,方差也不小。再从箱型图可看出,越界的数非常多。

  • 所以非常有必要进行异常值处理,再结合后来的回归效果可知,处理后比不处理效果好很多,随后也是对他进行3sigma异常值处理。

runtime
print(np.sort(data.runtime)[7300:])
np.sort(data.runtime)[:50]
[172. 174. 174. 174. 174. 175. 175. 175. 175. 175. 176. 176. 177. 177.
 178. 178. 178. 178. 178. 178. 178. 179. 179. 179. 179. 180. 180. 180.
 180. 180. 181. 181. 181. 181. 182. 183. 183. 183. 183. 183. 184. 185.
 185. 186. 186. 186. 187. 187. 188. 188. 188. 189. 189. 189. 189. 190.
 191. 191. 192. 192. 193. 193. 193. 194. 195. 195. 197. 197. 199. 199.
 200. 201. 201. 202. 206. 207. 207. 208. 208. 212. 213. 214. 216. 219.
 220. 224. 225. 238. 248. 254. 320. 338.  nan  nan  nan  nan  nan  nan]





array([ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,
        0.,  0.,  0.,  0.,  0.,  0.,  0.,  0., 11., 25., 26., 38., 40.,
       40., 40., 42., 44., 46., 53., 57., 59., 60., 62., 63., 63., 64.,
       65., 65., 65., 66., 66., 66., 66., 67., 68., 68., 69.])
  • runtime也是进行了同样的操作

runtime和popularity做了归一化处理,为了和离散型的程度相似。

revenue做了对数平滑处理,缩小数据,减小数值的差距,特征值都是一些小于的数,目标变量不宜取得这么大。

2)离散型

  • 离散型有genres、original_language、production_companies、production_countries、spoken_languages、Keywords、cast、crew

  • 他们都是一些json格式的数据,我们要在里面提取出有代表性的数据,例如id\name等等

  • 下面的函数是用来处理json格式的,把它变回字典的形式,我们就可以提取出里面的信息了
def jsonChange(strs,key):                      #改进后的处理json格式的方法,采用eval函数
    '''
    函数说明:处理json文本,使其可加载(loads)出来
    :param strs: json文本
    :param key: 要提取信息的字典中的key
    :return: list或者NaT--存放目标信息的list
    '''
    if type(strs)==float:
        return []
    strs = eval(strs)
    if strs:
        return list(i[key] for i in strs)
    else:
        return []
np.array(data.genres.apply(jsonChange,key='id'))
array([list([35]), list([35, 18, 10751, 10749]), list([18]), ...,
       list([18]), list([27, 53]), list([18])], dtype=object)
  • 这样就可以把里面的类型的ID提取出来了
data['new_genres'] =data.genres.apply(jsonChange,key='id')
data['countries'] = data.production_countries.apply(jsonChange, key='iso_3166_1')
data['companies'] = data.production_companies.apply(jsonChange, key='id')
data['languages'] = data.spoken_languages.apply(jsonChange, key='iso_639_1')
data['cast_name'] = data.cast.apply(jsonChange,key='id')
data['keywords'] =data.Keywords.apply(jsonChange,key='id')
#data['crew'] = data.crew.apply(jsonChange,key='id')
  • 创建新的列存放处理后的数据

  • crew和cast这两列信息非常多,都是选取名字对应的ID来作为特征

二、特征工程

1、特征选择

  • 选择离散型的genres、original_language、production_companies、production_countries、spoken_languages、Keywords、cast、crew
还有数值型型的popularity、runtime

电影时长和revenue的关系

plt.scatter(data.runtime,data.revenue.apply(np.log1p))
plt.title('对数化')
plt.show()
plt.scatter(data.runtime,data.revenue)
plt.title('没对数化')
plt.show()

欢迎度和revenue

plt.scatter(data.popularity,data.revenue.apply(np.log))
plt.title('对数化')
plt.show()

plt.scatter(data.popularity,data.revenue)
plt.title('没对数化')
plt.show()

  • 从上面的数据可以看出,revenue取对数后和特征相关性变高

genres种类及频数

id =[]
for i in data.genres:
    if type(i)!=float:
        for j in eval(i):
            id.append(j['id'])
print(np.array(pd.DataFrame({'a':id}).a.value_counts()))
np.array(set(id))
[3676 2605 1869 1735 1435 1116 1084  744  735  675  628  550  382  295
  267  243  221  117   84    1]

array({10752, 12, 14, 16, 10769, 18, 10770, 27, 28, 10402, 35, 36, 37, 9648, 53, 80, 99, 878, 10749, 10751},
      dtype=object)

原始语言及频数

original_language=[]
for i in data.original_language:
    original_language.append(i)
print(np.array(pd.DataFrame({'a':original_language}).a.value_counts()))
np.array(set(original_language))
[6351  199  118  109   95   90   56   49   49   46   41   31   20   17
   13   12   11    9    9    9    6    5    5    5    5    4    4    3
    3    3    3    3    2    2    2    1    1    1    1    1    1    1
    1    1]

array({'ka', 'he', 'ml', 'ur', 'vi', 'bm', 'ta', 'cs', 'tr', 'nb', 'ro', 'sr', 'is', 'th', 'te', 'nl', 'xx', 'ca', 'mr', 'it', 'de', 'af', 'cn', 'hi', 'ko', 'ja', 'zh', 'hu', 'en', 'ar', 'da', 'no', 'fr', 'pt', 'kn', 'bn', 'id', 'pl', 'el', 'ru', 'fa', 'fi', 'es', 'sv'},
      dtype=object)

电影语言及频数

spoken_languages=[]
for i in data.spoken_languages:
    if type(i)!=float:
        for j in eval(i):
            spoken_languages.append(j['iso_639_1'])
print(np.array(pd.DataFrame({'a':spoken_languages}).a.value_counts()))
np.array(set(spoken_languages))
[6449  711  559  419  364  321  204  163  160  102  100   89   78   69
   66   64   57   55   52   50   41   36   32   27   23   23   23   23
   20   19   17   17   17   15   15   12   11    9    9    9    8    8
    8    7    7    7    6    5    5    5    5    5    4    4    4    4
    4    4    4    3    3    3    3    3    3    3    2    2    2    2
    2    2    2    2    2    2    2    1    1    1    1    1    1    1
    1    1    1    1    1    1    1    1    1    1    1    1    1    1]

array({'mt', 'mk', 'wo', 'to', 'th', 'ce', 'sa', 'uk', 'mr', 'kk', 'zh', 'si', 'hu', 'en', 'qu', 'ga', 'da', 'sw', 'hr', 'bs', 'hy', 'ru', 'fa', 'fi', 'eo', 'ka', 'he', 'pa', 'ml', 'cs', 'ro', 'gd', 'zu', 'af', 'cn', 'hi', 'iu', 'tl', 'ny', 'fr', 'pt', 'ps', 'nv', 'kn', 'id', 'pl', 'ne', 'sv', 'so', 'bm', 'ta', 'tr', 'km', 'te', 'nl', 'eu', 'xx', 'gl', 'ca', 'ms', 'la', 'sq', 'my', 'xh', 'no', 'sh', 'el', 'bg', 'et', 'lo', 'yi', 'am', 'as', 'ur', 'gn', 'ty', 'st', 'vi', 'kw', 'jv', 'ku', 'sr', 'is', 'bo', 'ln', 'it', 'br', 'de', 'ko', 'mi', 'ja', 'ar', 'mn', 'bn', 'sk', 'cy', 'es', 'gu'},
      dtype=object)
  • 还有keyword,crew等被使用的数据、上映日期也跟revenue有着不少的关系、就把年份和月份都做成了单独一列,作为回归的特征
在该部分(预测revenue)没用上title、tagline、original_title文本特征,后面附加的分类会用到。
  • 从上面数据可看出,各种选取的特征都是挺明显的

2、特征转换

  • 建立一个新的数据框架,将需要的数据都放进去

  • 例如:

onehotDataFrame =pd.DataFrame()
genre_kinds =[10752, 12, 14, 16, 10769, 18, 10770, 27, 28, 10402, 
              35, 36, 37, 9648, 53, 80, 99, 878, 10749, 10751]
for i in genre_kinds:
    onehotDataFrame['genre_'+str(i)]= data.new_genres.apply(lambda col:1 if i in col else 0)
  • 创建二进制列,每个类型都作为一个列

  • 有些特征有几百几千种值,也不可能全部在把他们加进去,后面将会谈到

三、模型构建

  • 刚开始用普通最小二乘法回归,因特征过多,容易过拟合,后改用岭回归削弱低权重的特征

  • 把特征工程得到的新的数据集将每个例子放进二维数组,也不是全部特征都适合加进去,虽然岭回归可以削弱一些特征的权重,但依然是对结果有影响

1、过拟合处理
下面来确定放进新数据集的特征的数目(这是个人的方法,当然可以用迭代得到正确率来决定,但有时候没能求出正确率,就可以用这种方法来评估)
  • 在基于普通最小二乘法算法下,对所有特征进行拟合,就是说所有特征都加进去,拟合,比如公司种类有3千多间,全部都变成二进制列加进去,有该公司则天1,无则填0;

所以最后回归方程的X值有三千多个。

这样的做法在训练集上是可观的,训练得分0.92,但放在测试集时就不是这回事了,最终预测出来的又上千个0,几百个无穷大,这就是自变量太多造成的,就是特征太多,每个特征都同等重要。
  • 于是想要找到最适合的特征加入量,拿电影出产公司举例子,公司数目3000多。
用for循环来改变公司数目range(30,600,20),得到训练集分数,和测试集revenue的无穷大数目,0值数目,随加进去拟合的公司数目的变化,如下图所示

在这里插入图片描述
横坐标乘以30就是公司的数目了,可以看出,训练集分数没什么变化,其实是在逐渐上升的,因为特征值数目越多分数就会越高,只不过纵轴值太大,很看出一个小于1的百分数的变化

然后在10之前,要不是出现无穷大就是出现0值,这都是不正常的,在10之后也就是公司数目为300左右,0值和无穷大值就没有了,座椅我们应该选取这个公司数目加加人作为特征。

  • 接下来我们要确定岭回归的lambda值,就是惩罚值,图中列出训练集分数和测试集最大值(发现最大值可表示着整体的水平)(因为要让数据不走向无穷大)
    在这里插入图片描述
    可以看出在lambda取14左右的时候,分数来开始下降,最大值也开始下降,表明预测出来的revenue开始整体下降了
    在这里插入图片描述
    这个是各个特征的系数随lambda值的变化图,也可以看出从14,15左右开始变稳定

  • 我们要求训练集分数的同时也要兼顾测试集最大值(因为发现训练出来的revenue是整体偏大的,需要让他们下降)
    图一
    在这里插入图片描述
    上边的是训练集的revenue,下边的是测试集的revenue,可以看出确实是整体变高了
    最后取14作为lambda值。

2、评估模型

采用交叉验证,用一下方法随机分离测试集和训练集


        test_size = 0.25

        data_class_list = list(zip(x, y))          #zip压缩合并,将数据与标签对应压缩

        random.shuffle(data_class_list)                             #将data_class_list乱序

        index = int(len(data_class_list) * test_size) + 1           #训练集和测试集切分的索引值

        train_list = data_class_list[index:]                        #训练集

        test_list = data_class_list[:index]                         #测试集

        train_data_list, train_class_list = zip(*train_list)        #训练集解压缩

        test_data_list, test_class_list = zip(*test_list)           #测试集解压缩

同sklearn的

sklearn.model_selection.train_test_split

然后再在外围加个for循环就可以进行多次交叉验证,再求个平均值就可以很好地评估了该模型的正确率,可以对测试集的预测结果有个底。

我们也可以通过这个评估模型来训练本身,因为我们加入的特征数量都是自己定的,而定多少就会影响到最终结果,在3.1过拟合分析也讲到确定特征数量的问题,那个是个人方法,我们一般采用比较好的参考值(即正确率)来作为评估值,逼不得已才需要想出、观察出其他的参考值。如下面的代码所示,featureNum就是我们需要加入的特征数量。

#岭回归
    for featureNumin range(320, 420, 5):  # 调试用,调试加入==特征的数量、岭回归的lamada值==等
        forecast = mySklearn(preprocess,featureNum)  
        '''岭回归
        r2, RMSE ,coef = forecast.ridge()
        r2list.append(r2)
        RMSElist.append(RMSE)
        '''
    
        '''贝叶斯'''
        forecast.dmTree()

在模型里面收集正确率,然后绘图,如下:
在这里插入图片描述
在这里插入图片描述
找出正确率高的区间,然后再细分,如此类推,最终找到满意的参数即可。

附加

前面提到的title、tagline、original_title文本特征,现在用于作分类,用朴素贝叶斯来处理文本特征,然后进行分类。此处的分类是电影类型的分类,一部电影可以属于多种类型,所以这个问题属于多标签分类,也就是multi—label .

学习贝叶斯原理的可以移步到这里 https://blog.csdn.net/c406495762/article/details/77341116?utm_source=app

其中的是不调库实现前面数据处理的部分,包括句子分词,词汇量排序,去停用词(a, the, what, how 这种,什么文章都有的这种,不能够清晰地分辨一篇文章的类型),组成特征词,构成词汇稀疏矩阵,然后就OK了(1),如果依然不掉库做二分类的话,就是训练出贝叶斯词频,然后再作用于测试集就行。

但是我们的问题是多标签的分类,不掉库实现多标签的话较麻烦,所以就直接调库了,如下:

from sklearn.multiclass import OneVsRestClassifier
from sklearn.pipeline import  Pipeline
def TextClassifier(train_feature_list, test_feature_list, train_class_list, test_class_list):

    clf = OneVsRestClassifier(MultinomialNB())#多标签处理器

    classifier = clf.fit(np.array(train_feature_list), train_class_list)#拟合

    pre = clf.predict(np.array(test_feature_list))#预测
    
    test_accuracy = classifier.score(test_feature_list, test_class_list)#得分
    
    num = 0
    denom =0
    for i,j in zip(pre, test_class_list):#总体正确率:预测正确的标签数量/总标签数量
        h = i+j#两个np.array相加,出现2的话,就表明预测正确
        denom = denom + sum(j)

        for k in h:
            if 2 ==k:#出现2
                num=num+1
    single_accuracy = num/denom

    num_0 =0
    for i,j in zip(pre, test_class_list):#单标签正确率:凡是有一个类型预测正确,这部电影就算是预测正确了
        h = i+j
        for k in h:
            if 2 ==k:
                num_0=num_0+1
                break
    print("单标签正确率:", num_0 / len(pre))

    return test_accuracy, pre, single_accuracy

这里的
单标签正确率:电影的一个类型预测正确,就属于预测正确了
总体正确率:一部电影往往有多个标签,比如总标签是4个,预测到的有2个,则正确率为0.5。

单计算title的话:在这里插入图片描述
单计算tagline的话:在这里插入图片描述
单计算original_title的话:在这里插入图片描述

三个合并起来:在这里插入图片描述
可见,团结就是力量,不过也不一定是越多越好。

具体实现就,参考源码吧。

git:https://github.com/linkunxin/filmRvenuePred

百度网盘:https://pan.baidu.com/s/1_rpzLeTFf14x6KoxwWY4Lw

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值