R&S | 手把手搞推荐[1]:数据探索

要搞类似的算法项目,第一件事不是就整建模,而是了解数据,今天不多说别的复杂模型,就来谈谈,怎么对问题进行分析,对数据进行探索,从而为未来的建模和解决问题提供有力帮助。

懒人目录

  • 目标确定
  • Moielens
  • 数据探索
  • 数据集整理
  • 再来一次数据探索
  • 小结

目标确定

确认目标是一件事开始的第一步,道理大家都懂,但是能做到对问题有完整定义的却寥寥无几。

本系列,我要用Movielen提供的数据集作为基础,自己建立一个推荐系统。

具体功能,就是根据给定用户,提供尽可能好的推荐内容,好的标准在于给他推荐的内容(测试集下)评分较高。

Movielens

首先来看看数据,Movielens是从官网上收集的电影评级数据,包括部分用户信息、电影信息和最终的评分结果。

此处,我们以“MovieLens 1M Dataset”作为数据,根据介绍显示,有4000部电影,6000名用户,100万评级。

资源维度和用户维度数据其实并不是很多,但是由于评级量大,数据量其实足够建模,应该不会有什么大问题。

数据探索

数据探索的基本操作

有了数据就开始建模?肯定不是,相信我,数据探索绝对是值得你花时间的重要一步。

首先,要知道数据的内容,有什么数据,数据类型、字段是什么,每个字段下的数据类型是什么,离散的还是连续的等等,总结一下:

  • 数据的格式(CSV,excel,dat等)以及其字段
  • 字段数据类型,数字or文字,整数or分数,连续or离散,有限or无限等

然后,开始进行一些有关数据分布、相关性的分析。

  • 部分数据是否具有周期性,如时间序列上的
  • 数据具体的分布如何,尤其是和你目标直接相关的特征
  • 是否存在严重不平衡的特征

非常推荐你打开数据看上几行(对于数据结构比较简单的我甚至会话1个小时去看看),看具体有什么特点。这些特别的样本可能就是你未来的暗坑,先知道可能会有问题,到时候排查就会简单很多。

  • 有没有自己意想不到的情况,如性别为空,文字上存在多语言、标点符号等。

异常点检测,看看有没有很特别的样本,缺失值,数据记录明显有错(数据单位是万元,但是有一些几个亿的数据,基本可以判断是数据错误了),及时修正和更新,有时可能还要删除。

从上面的流程可以看到,数据探索是为了去了解数据内有什么特别地信息,让你更好地了解数据,这样才能让你后续建模思路更清晰,而不是在建模阶段才来翻看数据说明,这样效率会很低。

探索movielens-1m数据

说完上面的思路,下面就来看看在这里我是怎么做的。这里不包括所有数据操作,有些会在本文后续章节提到。

运气不错的是,他们给了我们一个非常完善的文档,里面有对数据的说明,所以数据的基本结构比较明朗(英文文档可以谷歌或者百度翻译哈~慢慢的要开始自己看英文文档)。

  • ratings.dat: UserID::MovieID::Rating::Timestamp
  • users.dat: UserID::Gender::Age::Occupation::Zip-code
  • movies.dat: MovieID::Title::Genres

具体每个字段的含义,可以在文档里面清晰看到,此处不赘述。

他说有4000部电影,6000个用户,到底是否真的如此,我们可以通过一个简单的命令看看,Linux或者mac shell环境下,windows下可以下载git bash或者在vscode下使用,部分命令甚至可以在powershell下尝试。

wc -l movies.dat

通过该语句可以看到movies具体有多少行。另外还非常建议大家看看数据具体是什么样的,例如我们就看前50行。

head -50 movies.dat

运行成功后你可以看到前50行的内容,通过仔细看看也是能获得一些比较重要的信息,这些信息在后续建模中是十分有用的。来举个例子吧。

12::Dracula: Dead and Loving It (1995)::Comedy|Horror

这是一条电影的数据,出乎意料的是,电影名上还带有电影上映的时间,这个时间可能可以在后续作为重要特征放入模型中。

数据集整理

在对数据有非常初步的认识后,可以尝试通过集成数据后在进行进一步的分析和讨论。

为了更快接近目标,可以把数据合并起来,整理成与未来进行分类相似的形式,这里非常推荐使用python中的pandas。

这里我想啰嗦一下,平时其实自己并不喜欢用pandas,直接使用数组进行操作是我的常态,加上numpy已经是极限了,这里喜欢用pandas的原因是他对数据合并具有很强的效果,我甚至觉得他有类似sql的功能,在进行表级别查询和筛选时我就会选择用pandas。

完整代码

下面是一套完整代码,处理了一套合并后的完整数据集,后续也分为了训练集和测试集,代码比较稚嫩,欢迎各位大佬提出意见。觉得看大块代码太痛苦的继续往下翻,我会有分解动作。

# 目标是构建一个可供训练和测试的数据集

import os
import pandas as pd
from sklearn.model_selection import train_test_split

MOVIE_PATH = "../../data/ml-1m/movies.dat"
RATING_PATH = "../../data/ml-1m/ratings.dat"
USERS_PATH = "../../data/ml-1m/users.dat"
SET_PATH = "../../data/ml-1m_20190508"
MOVIE_RATING_PATH = "%s/rating_combine_20190508.csv" % SET_PATH
TRAIN_RATING_PATH = "%s/TRAIN_20190508.csv" % SET_PATH
TEST_RATING_PATH = "%s/TEST_20190508.csv" % SET_PATH

if not os.path.exists(SET_PATH):
    os.makedirs(SET_PATH)

# 读取数据
movies = pd.read_csv(MOVIE_PATH, sep="::", header=None, names=[
                     "movieId", "movieName", "genres"], engine='python')
users = pd.read_csv(USERS_PATH, sep="::", header=None, names=[
                    "userId", "gender", "age", "occupation", "zipcode"], engine='python')
rating = pd.read_csv(RATING_PATH, sep="::", header=None, names=[
                     "userId", "movieId", "rating", "timestamp"], engine='python')

# 数据合并
data = pd.merge(movies, rating, on="movieId")
data = pd.merge(data, users, on="userId")

# 信息组合
data = data[["rating", "movieId", "movieName", "genres", "userId", "gender",
             "age", "occupation"]]
data.to_csv(MOVIE_RATING_PATH, index=False, sep="@")

# 训练集和测试集组合
X_train, X_test, y_train, y_test = train_test_split(data[["rating"]], data[[
                                                    "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]], test_size=0.33, random_state=10)
train_set = y_train.join(X_train)[
    ["rating", "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]]
test_set = y_test.join(X_test)[
    ["rating", "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]]
train_set.to_csv(TRAIN_RATING_PATH, index=False, sep="@")
test_set.to_csv(TEST_RATING_PATH, index=False, sep="@")

分解动作

数据集存储文档命名与初始化

大概对应代码的下面这段:

MOVIE_PATH = "../../data/ml-1m/movies.dat"
RATING_PATH = "../../data/ml-1m/ratings.dat"
USERS_PATH = "../../data/ml-1m/users.dat"
SET_PATH = "../../data/ml-1m_20190508"
MOVIE_RATING_PATH = "%s/rating_combine_20190508.csv" % SET_PATH
TRAIN_RATING_PATH = "%s/TRAIN_20190508.csv" % SET_PATH
TEST_RATING_PATH = "%s/TEST_20190508.csv" % SET_PATH

if not os.path.exists(SET_PATH):
    os.makedirs(SET_PATH)

代码估计比较简单,只要对基本语法熟悉就能看懂。所以我简单把几个要点说下。

  • 我个人喜欢文件名之类的固定值提前定义好,甚至是一些训练的参数放在前面,大写字母表示
  • 按照日期或者日期+号码的方式命名后缀,可以记录自己的各种更新和改变,甚至可以在文件夹内部加上README记录必要的细节和区别。

数据读取

大概对应代码的下面这段:

# 读取数据
movies = pd.read_csv(MOVIE_PATH, sep="::", header=None, names=[
                     "movieId", "movieName", "genres"], engine='python')
users = pd.read_csv(USERS_PATH, sep="::", header=None, names=[
                    "userId", "gender", "age", "occupation", "zipcode"], engine='python')
rating = pd.read_csv(RATING_PATH, sep="::", header=None, names=[
                     "userId", "movieId", "rating", "timestamp"], engine='python')
  • 根据探索的数据,按照一定的格式读取,因为我后需要用到pandas,所以这里建议大家用pandas的方式读取乘dataframe格式,方便后续使用,如果没这个需求,那用csv设置IO流的方式读取,其实都没关系,处理好即可。

数据合并

大概对应代码的下面这段:

# 数据合并
data = pd.merge(movies, rating, on="movieId")
data = pd.merge(data, users, on="userId")

# 信息组合
data = data[["rating", "movieId", "movieName", "genres", "userId", "gender",
             "age", "occupation"]]
data.to_csv(MOVIE_RATING_PATH, index=False, sep="@")
  • pd.merge进行合并,非常简单方便,还有一些更复杂的操作可以去看pandas的API
  • 信息组合下的这一行data[["rating", "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]]其实就体现了pandas类似SQL的功能,这里这么整的意思是按照一定顺序来读取列,保证列是按照我们的需求排列的
  • to_csv是pandas下dataframe的函数,具体含义自己去查哈。

训练集测试集划分

大概对应代码的下面这段:

# 训练集和测试集组合
X_train, X_test, y_train, y_test = train_test_split(data[["rating"]], data[[
                                                    "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]], test_size=0.33, random_state=10)
train_set = y_train.join(X_train)[
    ["rating", "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]]
test_set = y_test.join(X_test)[
    ["rating", "movieId", "movieName", "genres", "userId", "gender", "age", "occupation"]]
train_set.to_csv(TRAIN_RATING_PATH, index=False, sep="@")
test_set.to_csv(TEST_RATING_PATH, index=False, sep="@")
  • train_test_split是非常好的数据集划分工具。
  • dataframe.join是一个合并dataframe的优良工具,与str.join是两个函数,这里注意。

通过上述步骤,数据集就构建完成了。

再来一次数据探索

什么??为啥还要一次,其实是因为有些分析合并前不好做,所以集成之后,和最终预测的数据结构类似,非常利于进行分析和计算,在python层我做简单这些分析,还不完善。

import pandas as pd

MOVIE_RATING_PATH = "../../data/rating_combine_20190506.csv"

combine_data = pd.read_csv(MOVIE_RATING_PATH, sep="::",engine='python')
pd.set_option('display.max_rows', 1000)

# 随便找个人看看打分的分布
print(combine_data[["userId", "movieId", "rating"]][combine_data["userId"]==9].groupby(by='rating').count())
print("------------------------------------------------------")

# 随便找个电影看看打分的分布
print(combine_data[["userId", "movieId", "rating"]][combine_data["movieId"]==20].groupby(by='rating').count())
print("------------------------------------------------------")

# 统计每个用户评论电影的数量
print(combine_data[["userId", "movieId", "rating"]].groupby(by='userId').count())
print("------------------------------------------------------")

# 统计每个电影被评论的数量
print(combine_data[["userId", "movieId", "rating"]].groupby(by='movieId').count())
print("------------------------------------------------------")

# 统计给电影打分次数的分布
print(combine_data[["userId", "movieId", "rating"]].groupby(by='userId').count().groupby(by='rating').count())
print("------------------------------------------------------")

看看具体的案例,从一个人的角度,一部电影的角度等,去进行分析,分析的目标有这几个:

  • 有没有比较特别的案例
  • 是否存在数据不平衡的问题
  • 单特征样本量是否会不足

小结

本文主要给大家谈到了数据探索的原因和方法,也给大家提供了代码甚至是分解动作,在这里简单总结一下。

  • 数据探索是一个对数据有深入了解的过程,对数据都不了解谈不上解决问题,做饭总得知道冰箱里有啥菜,够不够吃,要不要再去买,一个道理。
  • 所谓得了了解数据,除了知道有什么,还要知道很多细节信息,数据类型,数据平衡问题,缺失问题等。
  • 有些工作通过shell的角度可以快速解决,python有时候会太拖沓。
  • pandas在进行数据查询之类操作十分高效,欢迎尝试。
    • 提醒一个暗坑,pandas似乎有点吃内存。

好了,现在对数据有基本的了解了,下一篇开始我就开始弄第一个基线模型,尝试用LR来进行用户打分预估,敬请期待。

做个小广告,如果对我的文章感兴趣,欢迎关注我的个人微信公众号:CS的陋室。

你可能感兴趣的:(R&S | 手把手搞推荐[1]:数据探索)