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的陋室。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容