我们这个系列共分为三个部分:数据抓取,数据整理,数据分析应用,前面有一篇帖子已经讲述了数据抓取部分(需要的话可以点我发的帖子或者关注我们微信,帖子里代码有点小错误,应景更改了),这篇文章我们主要讲抓取过来的数据怎么整理,然后我们会发出多篇用抓取过来的数据写就的文章,请大家轻喷。
我们的目的简单,从数据电影属性中分离出上映电影的国家、时间、类型、导演和演员。
首先我们要把上一步抓取的数据读进来:
- #filmData <-read.csv("E:/自媒体/filmData.csv", header = T, sep =",", stringsAsFactors = F)
- #filmData <-filmData[, -1]
以上两步可能和你存储的方式有关,filmData是抓取数据存放的csv格式的文件,如果你写入硬盘然后读尽来,会发现多了一列row.names叫做x,这里将他删除。如果你没有写入硬盘则无需删除操作。我们的电影都存放在filmAttri列。
- sentence <-as.vector(filmData$filmAttri)#将电影属性提出来转化为向量,准备下一步分解
- temp1 <-strsplit(sentence, "/")#将属性按照/分开
- filmAttribute <-as.data.frame(cbind(rep(filmData[,1], unlist(lapply(temp1, length))),
- rep(filmData[,2], unlist(lapply(temp1, length))),
- unlist(temp1)),stringsAsFactors = F)#将属性与电影名称组合成一个dataframe
- colnames(filmAttribute)<- c("id", "name", "attri")
首先我们将属性列提取出来放到sentence里,观察一下发现这些属性是由“/”分开的,那么我们就用strsplit将其分开,然后我们就获得了一个和电影的部数一样长的list,list的每个vector包含一部电影的所有属性,要将电影的名称和属性对上,那么vector有多长,就要重复多少次相应的电影名称,然后我们又新加了一个电影id列,每一个id号对应一部电影,这样做是方便下面操作。
因为有些影片没有国家,有些影片没有时间,所以我们需要第一步删选出国家和时间均不空缺的记录,这就是一个如何实现多次筛选的问题:
- #如何实现多次筛选
- x <-as.character(c(2000:2015))#筛出年份
- time <-filmAttribute[filmAttribute$attri %in% x, ]#筛出年份
- filmAttribute <-filmAttribute[filmAttribute$id %in% time$id,]#去除没有年份的记录,这种筛选是按行名称提取的
以上我们就完成了第一次筛选,我们把缺少年份的电影全部删除了。
- rownames(filmAttribute)<- seq(1:length(filmAttribute[,1]))#因为删除行后行名称并没有重新标注,我们这里给它重新标注
上面我们删除了数据框filmAttribute中缺乏年份的电影,同时我们也要把原始数据框filmData里的也给删除了,保持两个数据的电影一致。
- filmData <-filmData[filmData$id %in% time$id,]#去除没有年份的记录
- rowname <-seq(1:length(filmAttribute[,1]))#因为后面的数据我是以行编码为依据提取的,为了防止乱掉创造了新列rowname,相当于属性的id
- filmAttribute <-data.frame(rowname, filmAttribute)
- x <-as.character(c(2000:2015))#筛出年份
- time <- filmAttribute[filmAttribute$attri%in% x, ]#筛出年份
以上我们就筛出了年份列,数据框time里一共4列,rowname用来标记电影时间所在的行号,id指具体的电影帮助对应到电影,name指电影的名称,attri下全是电影上映的时间。下面我们要筛出国家,一般一部电影的第一行属性即为国家和地区,当然有些不是,我们暂且把第一行作为国家处理:
- country <-filmAttribute[!duplicated(filmAttribute[, 2]),]#因为影片属性的第一行即为国家,当然有可能不是,这里先假设第一行全部是国家,去重函数分函数根据id保证电影都是唯一的
在R语言中,duplicated函数返回的是逻辑值TRUE和FALSE,如果某一行第一次出现,他被认为是独特的,duplicated返回的值是FALSE,如果不是第一次出现,则是重复的行,返回的值是TRUE,如果是对一个vector去重,则返回一个逻辑值vector,长度和vector一样。这里我们需要找到每一部电影的第一行,然后提起出来,那么我们加一个!非的操作就可以把每部电影的第一行数据,然后将这行的属性作为国家就行了。
然后我们需要提取电影的类别了,我们发现电影的类别一般在国家和上映时间所在行之间,于是我们之前加的列rowname有用了:
- temp1 <-country$rowname
- temp2 <-time$rowname
大部分电影国家地区(第一行)和年份之间的部分为电影类型,但是那么多电影也许中间会什么都不隔,所以要先判断一下加以区分,在做这个之前我们要先把缺少国家和地区的电影删除,记得我们前面经过一次筛选,筛出的电影都是含有上映时间的,那么如果一部电影缺少国家,他的第一行肯定是时间,那么时间行和国家行的rowname肯定是相同的,他们的差值为零,我们找出这样的电影将其删除了,就保证了剩下的电影既包含国家又包含上映时间。
- x <- temp2 -temp1
- x <-temp1[which(x == 0)]
- x <-country[country$rowname %in% x,]
- x <-filmAttribute[filmAttribute$id %in% x$id, ]#经检查晒出来的x里确实是缺少国家地区的电影,这里予以删除
在删除之前我们要看一下是不是这样的,结果我们发现x里面的电影确实没有国家这个属性,自然就要把他们删除掉了。
- filmAttribute <-filmAttribute[!(filmAttribute$id %in% x$id), ]
- filmAttribute <-filmAttribute[,-1]#删除缺少国家地区的记录,重新编号重新提取时间和地区
- rownames(filmAttribute)<- seq(1:length(filmAttribute[,1]))
- rowname <- seq(1:length(filmAttribute[,1]))
- filmAttribute <-data.frame(rowname, filmAttribute)
这里你会发现既然删除了一些电影那么我们之前提取的时间也没用了,所以干脆重来一遍,当然如果不重来用匹配也行,于是我们删除了第一列的rowname然后给属性重新编制id列仍然叫做rowname。然后依次提取时间、国家、电影类型等。
#提取时间和国家
- x <-as.character(c(2000:2015))#筛出年份,我们的跨度是16年,如过你没那么多,就照着你抓取的年份做
- time <-filmAttribute[filmAttribute$attri %in% x, ]#筛出年份
- country <-filmAttribute[!duplicated(filmAttribute[, 2]),]#因为影片属性的第一行即为国家,当然有可能不是,这里先假设第一行全部是国家
- filmData <-filmData[filmData$id %in% time$id,]#清除有年份没国家地区的记录
- temp1 <- country$rowname
- temp2 <-time$rowname
再次检验是否存在缺少国家的记录,如果通过则进行提取影片类型的过程:
- x <- temp2 -temp1
- x <-temp1[which(x == 0)]
- x <-country[country$rowname %in% x,]
- x <-filmAttribute[filmAttribute$id %in% x$id, ]#经检验不存在
下面我们要提取电影类型了,一般国家地区和年份之间的行即为影片类型,那么存在两种情况,如果国家地区和年份之间行号差值x小于2,则说明影片没有分类,如果大于等于则说明有分类:
- #提取电影类型
- x <- temp2 -temp1
- temp1 <-temp1[which(x > 1)]
- x <- x[which(x> 1)] - 1
- temp <- c()
- for (i in1:length(x)) {
- temp2 <- temp1[i] + seq(1:x[i])
- temp <- c(temp, temp2)
- }#生成含有影片类型的rowname,然后我们就知道那些rowname对应的是电影类型了,为什么减1,自己比划一下
- type <-filmAttribute[filmAttribute$rowname %in% temp, ]#提取影片类型
我们得到了电影的上映时间,国家或地区,电影类型等,现在我们先把他们合并在一起了,这里牵涉到你是将数据进行long型数据框摆放还是进行wide型数据框摆放,因为ggplot一般使用long型数据,这里我们进行long型摆放。
- ###将time, country, type先合并成一个data.frame
- variable <-rep("上映时间", length(time[, 1]))
- time <-data.frame(time, variable, stringsAsFactors = F)
- variable <-rep("国家地区", length(country[, 1]))
- country <-data.frame(country, variable, stringsAsFactors = F)
- variable <-rep("类型", length(type[, 1]))
- type <-data.frame(type, variable, stringsAsFactors = F)
- filmtemp <-rbind(time, country, type)
到这里我们已经得到电影的部分属性了,下面我们需要提取电影的导演和演员了,我们将我们已经提取的行删掉,就只剩下导演和演员了,一般出现的一个人名即为导演,其后是演员,,所以思路和提取国家时一样,去重得到的就是导演,我们按照这个思路往下走吧。
- #提取导演
- filmda <- filmAttribute[!(filmAttribute$rowname%in% filmtemp$rowname), ]#删除已经提取的行
- director <-filmda[!duplicated(filmda[, 2]),]
- actor <-filmda[duplicated(filmda[, 2]),]
- variable <-rep("导演", length(director[, 1]))
- director <-data.frame(director, variable, stringsAsFactors = F)
- #提取演员
- variable <-rep("主演", length(actor[, 1]))
- actor <-data.frame(actor, variable, stringsAsFactors = F)
- filmtemp <-rbind(filmtemp, director, actor)
- filmData <-filmData[filmData$id %in% filmtemp$id,]
- filmtemp <-filmtemp[, -1]
- filmfinal <-join(filmtemp, filmData)
- write.csv(filmfinal,"filmfinal.csv")
到这里我们的豆瓣数据电影数据抓取基本告一段落,代码虽然笨虽然长但是理解起来不难。下面我们要规范一下属性的内容,经查看发现国家和地区有些是英文有些是繁体,那么统一一下,幸好地区并不多,我们都懂建一个对应词典叫diqudict就行,yingyu列是要替换的国家英文名、繁体名及其他别称,第二列位统一的称呼。Filmfinal第三列就是属性的列,我们将其重匹配上的不标准行找到,然后用标准的替换掉,这就是一个用其自身匹配,然后又替换掉自身的问题。
- setwd("C:/Users/Administrator/Documents")
- #将国家为英文的翻译成中文,同同时统一国家称呼和写法
- diqudict <-read.csv("diqudict.csv", header = T, sep = ",",stringsAsFactors = F)
- filmfinal1 <-filmfinal
- t <-match(filmfinal1$attri, diqudict$yingyu)
- filmfinal1[filmfinal1$attri%in% diqudict$yingyu, 3] <- diqudict[t[!is.na(t)], 2]#这一点很烦,就是将匹配上的内容替换掉,这里采用了一种没加辅助列的方法,不知是不是还有其他更为直捷的途径
其实匹配从来都是一个让人蛋不定的问题,match返回的是x的元素在y中的位置,如果没有就为NA,如果多个就只返回第一个,返回的长度和x等长。经过一次match,这样我们去掉NA,就可以提取对应的字典里的统一的名称,然后将它放在filmfinal相应的位置。如果你看着代码不对,那就是论坛将% in %现实错了,在R里他是一个匹配函数,返回所有能够匹配上的x的编号。
然后有些国家同时出现了中文名称和英文名称,我们也要处理一下,方法就是先将这些记录提出来,然后将英文灭掉,再放回去。
- temp <-filmfinal1[which(filmfinal1$variable == "国家地区"),]#把国家地区记录单独提出来目的是去除同时出现了中英文的记录中的英文
- filmfinal1 <-filmfinal1[-which(filmfinal1$variable == "国家地区"),]
- sentence <-as.vector(temp$attri)
- sentence <-gsub("[a-zA-Z]", "", sentence)#去除英文字符
- attri <-sentence
- temp[,3] <-attri
- filmfinal1 <-rbind(filmfinal1, temp)#然后再和原来提取剩余的部分合并成原始文档
- #write.csv(filmfinal1,"filmfinal1.csv")#到这里还有极个别缺少国家和地区有毛病,写入硬盘手动添加和删除,然后再读入
到这里我们发现棒子的电影如果只有韩语名称,是无法正确识别的,我们需要将这部分电影删除:
- filmfinal1 <-read.csv("filmfinal1.csv", header = T, sep = ",",stringsAsFactors = F)
- filmfinal1 <-filmfinal1[-grep("^<.{6}>", filmfinal1$name), ]#删除个别几个只有韩语名称的电影,他们的特征是开头就是<
- filmfinal1[,2]<- gsub("<.{6}>", "", filmfinal1$name)#将名称中形如<U+0E1E>字符替换为空字符
- filmfinal1[,3]<- gsub("<.{6}>", "", filmfinal1$attri)#替换为""
- filmfinal1[,3]<- gsub("'", "", filmfinal1$attri)#替换为""
- filmfinal1 <-filmfinal1[-grep("[deleted]", filmfinal1$attri), ]
到这里我们只需要把名称为””的行删除就可以了,但是这里你碰到了麻烦,因为你查不出这个值为””的行,无论是is.na,is.null,is.nan都无法查出,好吧我们用正则表达式来解决吧:
- #解决空字符,但是is.na查不出的问题
-
- filmfinal1 <-filmfinal1[grep("\\S", filmfinal1$attri), ]#查了很多方法is.na,is.null,is.nan均不能查出""即双引号产生的空值,例如以上几行产生的空值,这里用正则表达式可以把这类空值筛掉
正则表达式\S表示匹配任意不是空白符的字符,那么我将非空白符的行提取出来就行了,自然就把那些””幽灵去掉了,如果你想找到这些幽灵,秩序在grep前面加个-号就行了,如果你用的是grepl就加!号。
最后我们将评分、人数空缺的全部填充为0。
- filmfinal1[is.na(filmfinal1[,5]),5] <- 0
- filmfinal1[is.na(filmfinal1[,6]),6] <- 0
- write.csv(filmfinal1,"filmfinal2.csv")#剩下的可以手动整理了
这里我们的R抓取豆瓣数据并整理数据的文章就写完了,下面我们就要展现基于这些数据的有趣的分析文章了,比如我们要看《2015国产电影到底有多糟?》
关于大音如霜工作室
关注理性与文艺,用数据创作内容性的精致阅读。关注请加微信公众号:dayinrushuang或扫描下方二维码


雷达卡




京公网安备 11010802022788号







