第2章:R语言中的数据管理与预处理

案例引入

电影是大众喜闻乐见的一种艺术形式。如今,电影市场中百花齐放,喜剧片、动作片、科幻片、动画片等电影类型争奇斗艳,各类元素文化在电影中都得以展现。

随着我国人民生活水平的提高,电影也成为不少家庭娱乐项目中的一个重要组成部分。从2008年的票房冠军《赤壁上》(2.7亿票房),再到2019年的票房冠军《哪吒之魔童降世》(49.3亿票房),中国电影票房一直保持快速增长态势。据《全球电影产业发展报告(2019)》统计数据显示,中国电影产业于2018年发展为全球第二。中国电影市场蓬勃发展的同时,也因为前景光明,利润巨大,从而导致行业规范模糊,从业人员鱼龙混杂,以至于烂片层出不穷,受观众诟病。电影品质良莠不齐,有的电影在时间的长河中历久弥新,有的电影却消失得无声无息。高评分电影反映了观众对于不同题材的喜好,针对高评分电影进行统计分析可以为日后电影拍摄提供有效支持。

本章节采用的是某电影榜单上排名top250的电影数据,数据集包含250部电影的名称、评分等数据。数据的变量说明表如下所示:

变量类型 变量名 详细说明 取值范围
属性 score 电影评分 [8.3,9.6]
type 影片类型 爱情、动作、动画等
duration 电影时长(分钟) [84,131]
rank 电影排名 [1,250]
nation 制片国家/地区 中国,美国,英国等
档期 showtime 电影上映时期 [1931/1/30,2017/11/24]
Year 电影上映年份 [1931,2017]
导演基本信息 director 导演名字 导演名字
rm(list = ls())#清空环境
movie <- read.csv("./data/top250.csv",fileEncoding = "gbk")#读取数据
head(movie)#电影数据示例
##   rank           name   showtime duration
## 1    1   肖申克的救赎  1994/9/10      142
## 2    2       霸王别姬   1993/1/1      171
## 3    3 这个杀手不太冷  1994/9/14      110
## 4    4       阿甘正传  1994/6/23      142
## 5    5       美丽人生 1997/12/20      116
## 6    6       千与千寻  2001/7/20      125
##          director type score nation
## 1 弗兰克·德拉邦特 剧情   9.6   美国
## 2          陈凯歌 爱情   9.5   中国
## 3       吕克·贝松 动作   9.4   法国
## 4          Robert 爱情   9.4   美国
## 5   罗伯托·贝尼尼 喜剧   9.5 意大利
## 6          宫崎骏 动画   9.2   日本

2.1 基本数据类型

2.1.1 数值型

数值型变量是一种定量数据类型,这类数据的取值是连续的。例如,电影数据集中的评分(score)就是用数值类型存放的。可以通过如下方式查看这一列对应的数据类型并对数值型数据进行加减乘除运算。

class(movie$score)#查看数据类型
## [1] "numeric"
#自己为变量附一个数值
a=2;class(a)
## [1] "numeric"
exp(1000)  # 正无穷
## [1] Inf
-10 / 0  # 负无穷
## [1] -Inf
exp(1000) / exp(990)  # NaN类型
## [1] NaN
exp(10)
## [1] 22026.47

2.1.2 字符型

字符型变量指用于存储文字的变量类型。在R语言中,用单引号或双引号定义的即是字符型数据。

#字符的定义
a = "2"
class(a)
## [1] "character"
# 判断电影数据集中,变量“类型(type)","电影名称(name)"是不是字符型变量
class(movie$name)
## [1] "character"
class(movie$type)
## [1] "character"

2.1.3 逻辑型

逻辑型数据即取值为TRUE或者FALSE的数据类型。

# 读入数据时设置把字符数据保留,不转换为factor
movie = read.csv("./data/top250.csv", header = T, stringsAsFactors = F, fileEncoding = "gbk")
movie$type[movie$name == "霸王别姬"] == "爱情" 
## [1] TRUE
# 想在数据集中挑选大于9分的喜剧电影名称(name)?
movie$name[movie$type == "喜剧" & movie$score > 9]
## [1] "美丽人生"       "三傻大闹宝莱坞"
## [3] "触不可及"       "两杆大烟枪"
# 逻辑语句加减
(1 == 2) + (3 < 4)
## [1] 1

2.1.4 因子型数据

因子型数据是R语言中比较特殊的一个数据类型,它用于存储类别型变量。例如:性别(男性、女性),年龄分段(未成年人、成年人)等。除存储取值水平无序的类别型变量外,因子型数据还可以设置类别变量各水平的次序。因子型数据可使用命令factor()来定义。

## 1.什么是因子型数据 ##
(genders = factor(c("男", "女", "女", "男", "男")))#生成因子型变量
## [1] 男 女 女 男 男
## Levels: 女 男
(class = factor(c("Poor", "Improved", "Excellent"), ordered = T))#设置因子水平高低
## [1] Poor      Improved  Excellent
## Levels: Excellent < Improved < Poor
## 2.改变因子型数据各水平的编码顺序 ##
(class = factor(c("Poor", "Improved", "Excellent"), ordered = T,
              levels = c("Poor", "Improved", "Excellent")))
## [1] Poor      Improved  Excellent
## Levels: Poor < Improved < Excellent
## 3.因子型和字符型数据互相转换 ##
# 输入原始字符变量
all = c("男", "女", "女", "男", "男")
# 将字符型变量变成因子型
gender = as.factor(all)
# 变换后的数据类型
is.factor(gender)
## [1] TRUE
class(gender)#判断gender的数据类型
## [1] "factor"

2.2 数据结构

rm(list = ls())#清理工作空间
movie = read.csv("./data/top250.csv",fileEncoding = "gbk",stringsAsFactors = FALSE)#导入数据

2.2.1 向量

向量(vector)是所有数据结构中最基础的形式,用于存储同一种类型数据的一维数组。

向量的基本操作包括向量创建、向量的索引提取以及集合的运算:

## (1)向量创建##
c(1, 1, 1, 2, 3, 3, 1, 2, 4, 1, 2, 4, 4, 2, 3, 4, 1, 2, 3, 4)
##  [1] 1 1 1 2 3 3 1 2 4 1 2 4 4 2 3 4 1 2 3 4
c("a", "b", "c", "d")
## [1] "a" "b" "c" "d"
# seq(起始值, 终止值, 步长)
seq(0, 10, by = 2)#生产0-10之间以2为间隔的等差数列
## [1]  0  2  4  6  8 10
1:10#生成1—10的连续数列
##  [1]  1  2  3  4  5  6  7  8  9 10
##(2)向量索引##
x<-c(1, 1, 1, 2, 3, 3)#生成向量
x[5]# 引用x向量中的第5个元素
## [1] 3
which(x == 3)#查看x向量中3所在的位置
## [1] 5 6
which.max(x)#查看x向量中最大值所在的位置
## [1] 5
which.min(x)#查看x向量中最小值所在的位置
## [1] 1
##(3)集合运算##
intersect(c(1, 2, 3, 3, 12, 4, 123, 12), c(1, 2, 3))#求交集
## [1] 1 2 3
union(c("狗熊会", "聚数据英才"), c("狗熊会", "助产业振兴"))#求并集
## [1] "狗熊会"     "聚数据英才" "助产业振兴"
setdiff(10:2, 5:3)#求差集
## [1] 10  9  8  7  6  2

常见的向量类型包括数值型向量、字符串向量,这里分别介绍两种向量类型的基本操作。

(1)数值型向量

以下介绍match(), cut(), sort(), order()函数:

## 数值型向量 ##
x<-c(10,6,4,7,8)#创建数值向量
min(x)#求最小值
## [1] 4
max(x)#求最大值
## [1] 10
range(x)#求范围
## [1]  4 10
# match函数
x <- c(1, 1, 1, 2, 3, 3, 1, 2, 4, 1, 2, 4, 4, 2, 3, 4, 1, 2, 3, 4)
(y <- letters[x]) # letters是一个内置字符串,里面储存26个字母字符
##  [1] "a" "a" "a" "b" "c" "c" "a" "b" "d" "a" "b" "d"
## [13] "d" "b" "c" "d" "a" "b" "c" "d"
match(y, letters[1:4])
##  [1] 1 1 1 2 3 3 1 2 4 1 2 4 4 2 3 4 1 2 3 4
x <- c("a", "c", "g", "h")
letters
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l"
## [13] "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x"
## [25] "y" "z"
match(x, letters)
## [1] 1 3 7 8
# cut函数
(Age = c(72,21,39,74,62,76,64,43,94,44,87,43,42,35,39,46,45,33,24,38))#生成向量
##  [1] 72 21 39 74 62 76 64 43 94 44 87 43 42 35 39 46
## [17] 45 33 24 38
# 将年龄数据离散化  
label = c('青年', '中年', '老年')  #设置标签
(ages = cut(Age, breaks = c(20, 35, 50, 100), labels = label))#划分区间
##  [1] 老年 青年 中年 老年 老年 老年 老年 中年 老年
## [10] 中年 老年 中年 中年 青年 中年 中年 中年 青年
## [19] 青年 中年
## Levels: 青年 中年 老年
# sort和order函数
(x = c(1,5,4,6,7))#生成向量
## [1] 1 5 4 6 7
sort(x)
## [1] 1 4 5 6 7
order(x)
## [1] 1 3 2 4 5
x[order(x)]
## [1] 1 4 5 6 7

(2)字符串向量

以下介绍nchar(), cutsubstr, paste(), grep(), gsub()函数:

# nchar用来提取字符串的长度
nchar("欢迎关注狗熊会")
## [1] 7
# 看看数据集中的电影名字的长度分别是多少
nchar(movie$name)
##   [1]  6  4  7  4  4  4  5  6  4  6  5  7  7  6  9
##  [16]  5  2  4  2  4  8  4  3  6  5  4  4  5  8  4
##  [31]  4  3  2  4  4  9  5  5  4  5  5  4  5  7  3
##  [46]  5  3  4  3  4  4  4  2  4  4  5  3  4  5  6
##  [61]  4  9  3  3  4  4  4  6  8  6  4  8  6  4  5
##  [76]  3  4  4  6  4  4  4  7  4  3  4  4  3  2  2
##  [91]  7 10  9  4  3  2  3  5  2  4  4  5  4  8  3
## [106]  2  5  3  7  4  5  4  3  4  5  4  4  5  4 10
## [121]  7  4  4  3  4  2  4  4  8  5  2  2  4  4  7
## [136]  5  3  4  4  4  5  5  5  6  7 13  4  3  5  4
## [151]  5  7  4  4  6  6  3  4  3  4  6  5  5  3  5
## [166]  2  4  2  3  3  3  2  4  2  4  5  4  4  7  4
## [181]  5  8  6  4  3  5  4  5  5  4  9  3  6 10  5
## [196]  2  5  2  4  6  4  4  4  4  3  5  8  5  4  5
## [211] 13  4  2  4  4 12  8  2  4  4  5  6  4  3  5
## [226]  7  5  5  2  2  4  5  5  5  5  2  4  2  4  7
## [241]  8  4  7  3  2  4  4  5  4  8
# 中英文的字符长度计算方法有不同
nchar("Welcome to follow the CluBear")
## [1] 29
# substr提取子字符串
substr("欢迎关注狗熊会", 1, 4)
## [1] "欢迎关注"
substr("一懒众衫小", 3, 5)
## [1] "众衫小"
# paste基本玩法
paste(c("双11", "是个", "什么节日"), collapse = "")
## [1] "双11是个什么节日"
paste("A", 1:4)
## [1] "A 1" "A 2" "A 3" "A 4"
# paste花式玩法
paste(1:4, collapse = "")
## [1] "1234"
paste(1:4, sep="")
## [1] "1" "2" "3" "4"
paste("A", 1:4, sep="_")
## [1] "A_1" "A_2" "A_3" "A_4"
txt = c("狗熊会", "CluBear", "双11", "生日")
# 返回含有关键字的字符位置
grep("Bear", txt)
## [1] 2
gsub("生日", "happy birthday", txt)
## [1] "狗熊会"         "CluBear"       
## [3] "双11"           "happy birthday"
# grep返回movie的director中包含“青春”的行号2,movie[2, ]即提取出movie数据集的第2行
(index <- grep("陈凯歌", movie$director))
## [1] 2
(cmovie <- movie[index, ])
##   rank     name showtime duration director type
## 2    2 霸王别姬 1993/1/1      171   陈凯歌 爱情
##   score nation
## 2   9.5   中国
salary = c("22万", "30万", "50万", "120万", "11万")
(salary0 = gsub("万", "0000", salary))
## [1] "220000"  "300000"  "500000"  "1200000"
## [5] "110000"
mean(as.numeric(salary0))
## [1] 466000
median(as.numeric(salary0))  # 结果是科学计数法的形式
## [1] 3e+05

2.2.2 矩阵

上一部分讲述的向量只能够展示一维数据信息。如果想要展示二维数据信息,则需要用到矩阵的组织形式。矩阵(matrix)是一个二维数组,矩阵每一个元素的数据类型相同。

首先,这里给出矩阵的创建与引用示例:

##(1)创建##
# 生成全部是0的矩阵
(zero = matrix(1:9, nrow = 3, ncol = 3))
##      [,1] [,2] [,3]
## [1,]    1    4    7
## [2,]    2    5    8
## [3,]    3    6    9
# 生成一个对角全是1的矩阵,直接在diag中输入对角线向量即可
(dig14 = diag(rep(1, 4)))
##      [,1] [,2] [,3] [,4]
## [1,]    1    0    0    0
## [2,]    0    1    0    0
## [3,]    0    0    1    0
## [4,]    0    0    0    1
##(2)创建##
# 从已有数据转化成矩阵
(M = matrix(1:12, nrow = 3, ncol = 4))
##      [,1] [,2] [,3] [,4]
## [1,]    1    4    7   10
## [2,]    2    5    8   11
## [3,]    3    6    9   12
# 生成指定对角元素的对角矩阵
(N = diag(1:4))
##      [,1] [,2] [,3] [,4]
## [1,]    1    0    0    0
## [2,]    0    2    0    0
## [3,]    0    0    3    0
## [4,]    0    0    0    4

下面介绍基本的矩阵操作方法:

## 矩阵概览 ##
# 查看矩阵的维度
dim(M) 
## [1] 3 4
# 提取矩阵的行数
nrow(M) 
## [1] 3
# 提取矩阵的列数
ncol(M)
## [1] 4
##  [1] 4
# 引用元素
M[1, 2]
## [1] 4
M[1:2, 2:3]
##      [,1] [,2]
## [1,]    4    7
## [2,]    5    8
# 给行列命名
colnames(M) = paste("x_", 1:4)
rownames(M) = 1:3; M
##   x_ 1 x_ 2 x_ 3 x_ 4
## 1    1    4    7   10
## 2    2    5    8   11
## 3    3    6    9   12
# 同样的命令可调用行列名
colnames(M)
## [1] "x_ 1" "x_ 2" "x_ 3" "x_ 4"
rownames(M)
## [1] "1" "2" "3"
# 将多个矩阵合并
(A = matrix(1:9, nrow = 3, ncol = 3, byrow = T))
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9
(B = diag(11:13))
##      [,1] [,2] [,3]
## [1,]   11    0    0
## [2,]    0   12    0
## [3,]    0    0   13
rbind(A, B) 
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    5    6
## [3,]    7    8    9
## [4,]   11    0    0
## [5,]    0   12    0
## [6,]    0    0   13
cbind(A, B) 
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    1    2    3   11    0    0
## [2,]    4    5    6    0   12    0
## [3,]    7    8    9    0    0   13

除了上述的基本操作,这里还介绍常用的矩阵数学计算操作:

A + B #矩阵的加法
##      [,1] [,2] [,3]
## [1,]   12    2    3
## [2,]    4   17    6
## [3,]    7    8   22
A - B #矩阵的减法
##      [,1] [,2] [,3]
## [1,]  -10    2    3
## [2,]    4   -7    6
## [3,]    7    8   -4
A * B #矩阵各元素对应相乘
##      [,1] [,2] [,3]
## [1,]   11    0    0
## [2,]    0   60    0
## [3,]    0    0  117
A %*% B #矩阵的乘法
##      [,1] [,2] [,3]
## [1,]   11   24   39
## [2,]   44   60   78
## [3,]   77   96  117
solve(B) #矩阵B的逆
##            [,1]       [,2]       [,3]
## [1,] 0.09090909 0.00000000 0.00000000
## [2,] 0.00000000 0.08333333 0.00000000
## [3,] 0.00000000 0.00000000 0.07692308
eigen(B) #矩阵B的特征值
## eigen() decomposition
## $values
## [1] 13 12 11
## 
## $vectors
##      [,1] [,2] [,3]
## [1,]    0    0    1
## [2,]    0    1    0
## [3,]    1    0    0

2.2.3 数组

数组(array)是向量和矩阵的推广,用于表达三维或者三维以上的数据。

##1.创建及引用##
# 创建数组
(result <- array(1:18,dim=c(3,3,2),dimnames = list(c("r1","r2","r3"),c("c1","c2","c3"),c("h1","h2"))))
## , , h1
## 
##    c1 c2 c3
## r1  1  4  7
## r2  2  5  8
## r3  3  6  9
## 
## , , h2
## 
##    c1 c2 c3
## r1 10 13 16
## r2 11 14 17
## r3 12 15 18
result[1,2,2]#获取单个元素
## [1] 13
result[1,,] #获取第一维度的数据
##    h1 h2
## c1  1 10
## c2  4 13
## c3  7 16
##2. 操作数组元素##
matrix1<-result[,,1]#获取数组中第1水平的矩阵
matrix2<-result[,,2]#获取数组中第2水平的矩阵
(add<-matrix1+matrix2)#矩阵相加
##    c1 c2 c3
## r1 11 17 23
## r2 13 19 25
## r3 15 21 27

2.2.4 数据框

数据框(dataframe)是实际数据处理中最常用的数据结构形式。数据框的每一行可以存储一条数据记录,每一列可以存储不同类型的变量。

首先介绍如何创建数据框:

### 1.创建数据框 ###
# 读入一个txt,csv等格式数据,即自成一个数据框
movie <- read.csv("./data/top250.csv", fileEncoding = "gbk", stringsAsFactors = F)
class(movie)
## [1] "data.frame"
# 自己创建
director <- c("陈凯歌", "宫崎骏", "李廷香","詹姆斯·卡梅隆", "刘镇伟", "周星驰", "李安", "姜文", "张艺谋", "吴宇森","岩井俊二", "王家卫", "陈可辛"  )
birthyear <- c(1952,1941,1964,1954,1952,1962,1954,1963,1950,1946,1963,1958,1962)
gender <- c("男", "男", "女", "男", "男", "男", "男", "男", "男", "男", "男", "男", "男")
directors <- data.frame(director, birthyear, gender); head(directors)
##         director birthyear gender
## 1         陈凯歌      1952     男
## 2         宫崎骏      1941     男
## 3         李廷香      1964     女
## 4 詹姆斯·卡梅隆      1954     男
## 5         刘镇伟      1952     男
## 6         周星驰      1962     男

下面介绍数据框的变形——长宽表转换的操作方法:

library(reshape2)
## (1) 宽表变长表 ##
mWide = data.frame(Name = c("A", "B"), Type = c("喜剧", "动作"),
                   GF2018 = c(6.5, 8.0), GF2019 = c(7.0, 7.5), GF2020 = c(8.1, 7.3))
                   # 由于构造数据框时列名不可以为纯数字,在数字前添加GF
# 将列名中的GF去掉
colnames(mWide)[3:5] = gsub("GF", "", colnames(mWide)[3:5])
mWide #查看原表
##   Name Type 2018 2019 2020
## 1    A 喜剧  6.5  7.0  8.1
## 2    B 动作  8.0  7.5  7.3
(mLong = reshape2::melt(mWide, id.vars = c("Name", "Type"), variable.name = "Year")) 
##   Name Type Year value
## 1    A 喜剧 2018   6.5
## 2    B 动作 2018   8.0
## 3    A 喜剧 2019   7.0
## 4    B 动作 2019   7.5
## 5    A 喜剧 2020   8.1
## 6    B 动作 2020   7.3
## (2) 长表变宽表 ##
# 长表变宽表
dcast(mLong, Name + Type ~ Year)
##   Name Type 2018 2019 2020
## 1    A 喜剧  6.5  7.0  8.1
## 2    B 动作  8.0  7.5  7.3

使用**ply族函数可以在R中对数据框进行向量化操作,从而实现数据透视表的功能,下面对此进行介绍。

library(dplyr)
# 根据电影类型进行分组,查看不同类型电影评分的平均水平
popular_type_grouped=group_by(movie,type)#根据电影类型进行分组
popular_type1=summarise(popular_type_grouped,
                        mean_score=mean(score),#计算不同类型的平均评分
                        max_score=max(score))#计算不同类型的最高评分
head(popular_type1)
## # A tibble: 6 x 3
##   type  mean_score max_score
##   <chr>      <dbl>     <dbl>
## 1 爱情        8.22       9.5
## 2 传记        7.97       9.1
## 3 动画        7.31       9.2
## 4 动作        8.01       9.4
## 5 犯罪        7.43       9.6
## 6 歌舞        8.95       9
#利用管道函数%>%省去中间变量命名
popular_type2 = movie%>%group_by(type)%>%
  summarise(mean_score=mean(score),#计算不同类型的平均评分
            max_score=max(score))#计算不同类型的最高评分
head(popular_type2)
## # A tibble: 6 x 3
##   type  mean_score max_score
##   <chr>      <dbl>     <dbl>
## 1 爱情        8.22       9.5
## 2 传记        7.97       9.1
## 3 动画        7.31       9.2
## 4 动作        8.01       9.4
## 5 犯罪        7.43       9.6
## 6 歌舞        8.95       9

2.2.5 列表

列表(list)是R语言中可以容纳各种类型的数据对象,如向量、矩阵、数据框,甚至一个列表也可以成为另一个列表的元素。

首先介绍列表的创建方法与基本操作:

##1.创建##
(example = list("abc", 3:5, matrix(1, nrow = 3, ncol = 4), data.frame(x = 1:4, y = paste0("boy_", 1:4))))
## [[1]]
## [1] "abc"
## 
## [[2]]
## [1] 3 4 5
## 
## [[3]]
##      [,1] [,2] [,3] [,4]
## [1,]    1    1    1    1
## [2,]    1    1    1    1
## [3,]    1    1    1    1
## 
## [[4]]
##   x     y
## 1 1 boy_1
## 2 2 boy_2
## 3 3 boy_3
## 4 4 boy_4
##2.基本操作##
# 查看
(complex = list(first = list(1:2), second = list(letters, list(matrix(1:4, nrow = 2, ncol = 2)))))
## $first
## $first[[1]]
## [1] 1 2
## 
## 
## $second
## $second[[1]]
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
## 
## $second[[2]]
## $second[[2]][[1]]
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
str(complex)
## List of 2
##  $ first :List of 1
##   ..$ : int [1:2] 1 2
##  $ second:List of 2
##   ..$ : chr [1:26] "a" "b" "c" "d" ...
##   ..$ :List of 1
##   .. ..$ : int [1:2, 1:2] 1 2 3 4
# 利用名字引用元素
complex$first  
## [[1]]
## [1] 1 2
# 利用序号引用元素
complex[[1]]
## [[1]]
## [1] 1 2
# 利用名字添加元素
complex$new = 1:5; complex
## $first
## $first[[1]]
## [1] 1 2
## 
## 
## $second
## $second[[1]]
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
## 
## $second[[2]]
## $second[[2]][[1]]
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## 
## 
## $new
## [1] 1 2 3 4 5
# 利用序号添加元素
complex[[3]] = matrix(1, 2, 3); complex
## $first
## $first[[1]]
## [1] 1 2
## 
## 
## $second
## $second[[1]]
##  [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"
## 
## $second[[2]]
## $second[[2]][[1]]
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4
## 
## 
## 
## $new
##      [,1] [,2] [,3]
## [1,]    1    1    1
## [2,]    1    1    1

与数据框相似,对列表也可以使用**ply族函数进行操作:

##3. 列表中的**ply函数##
# 老王耗子药的单价,单位(元/袋)
(price = list(year2014 = 36:33, year2015 = 32:35, year2016 = 30:27))
## $year2014
## [1] 36 35 34 33
## 
## $year2015
## [1] 32 33 34 35
## 
## $year2016
## [1] 30 29 28 27
# lapply返回列表
lapply(price, mean)
## $year2014
## [1] 34.5
## 
## $year2015
## [1] 33.5
## 
## $year2016
## [1] 28.5
# 求方差
lapply(price, sd)
## $year2014
## [1] 1.290994
## 
## $year2015
## [1] 1.290994
## 
## $year2016
## [1] 1.290994
# 求分位数
lapply(price, quantile)
## $year2014
##    0%   25%   50%   75%  100% 
## 33.00 33.75 34.50 35.25 36.00 
## 
## $year2015
##    0%   25%   50%   75%  100% 
## 32.00 32.75 33.50 34.25 35.00 
## 
## $year2016
##    0%   25%   50%   75%  100% 
## 27.00 27.75 28.50 29.25 30.00
# sapply默认返回向量或矩阵
sapply(price, mean)
## year2014 year2015 year2016 
##     34.5     33.5     28.5
sapply(price, sd)
## year2014 year2015 year2016 
## 1.290994 1.290994 1.290994
sapply(price, quantile)
##      year2014 year2015 year2016
## 0%      33.00    32.00    27.00
## 25%     33.75    32.75    27.75
## 50%     34.50    33.50    28.50
## 75%     35.25    34.25    29.25
## 100%    36.00    35.00    30.00
# mapply实现了将price与amount对应元素相乘的效果
(amount = list(year2014 = rep(200, 4), year2015 = rep(100, 4), year2016 = rep(300, 4)))
## $year2014
## [1] 200 200 200 200
## 
## $year2015
## [1] 100 100 100 100
## 
## $year2016
## [1] 300 300 300 300
(income_quarter = mapply("*", price, amount))
##      year2014 year2015 year2016
## [1,]     7200     3200     9000
## [2,]     7000     3300     8700
## [3,]     6800     3400     8400
## [4,]     6600     3500     8100

2.3 数据的读入与写出

rm(list = ls())#清理工作空间
movie = read.csv("./data/top250.csv",fileEncoding = "gbk",stringsAsFactors = FALSE)#导入数据

2.3.1 使用键盘输入数据

当读入数据较少时,可通过键盘输入数据。主要方式分为:直接输入以及利用R内置表格编辑器输入。

scores <- c(61,66,84,80,100)
#scores <- data.frame() #建立一个空数据框
#scores <-edit(scores)  #触发R内置编辑器
# 输入数据集
mydatatxt<-"name gender age
张三 M 20
李四 F 23
"
(mydata<-read.table(header = TRUE,text = mydatatxt)) # 读取数据集
##   name gender age
## 1 张三      M  20
## 2 李四      F  23

2.3.2 从带分隔符的文本文件导入数据

文本文件是一种常用的数据文件格式,函数read.table()可以从带分隔符的文本文件中导入数据,并生成一个数据框。常见的文本文件的数据格式为csv,读入csv文件可使用专有函数read.csv()。

### 1. read.rable() ###
#从txt中读入,分隔符为"\t"
tes = read.table("./data/top250.txt", header = TRUE, sep = "\t",fileEncoding = "GBK"); head(tes)
##   rank           name   showtime duration         director type score nation
## 1    1   肖申克的救赎  1994/9/10      142 弗兰克·德拉邦特 剧情   9.6   美国
## 2    2       霸王别姬   1993/1/1      171           陈凯歌 爱情   9.5   中国
## 3    3 这个杀手不太冷  1994/9/14      110       吕克·贝松 动作   9.4   法国
## 4    4       阿甘正传  1994/6/23      142           Robert 爱情   9.4   美国
## 5    5       美丽人生 1997/12/20      116   罗伯托·贝尼尼 喜剧   9.5 意大利
## 6    6       千与千寻  2001/7/20      125           宫崎骏 动画   9.2   日本
### 2. read.csv() ###
#专用函数read.csv
movie_csv = read.csv("./data/top250.csv",fileEncoding = "GBK"); head(movie_csv)
##   rank           name   showtime duration         director type score nation
## 1    1   肖申克的救赎  1994/9/10      142 弗兰克·德拉邦特 剧情   9.6   美国
## 2    2       霸王别姬   1993/1/1      171           陈凯歌 爱情   9.5   中国
## 3    3 这个杀手不太冷  1994/9/14      110       吕克·贝松 动作   9.4   法国
## 4    4       阿甘正传  1994/6/23      142           Robert 爱情   9.4   美国
## 5    5       美丽人生 1997/12/20      116   罗伯托·贝尼尼 喜剧   9.5 意大利
## 6    6       千与千寻  2001/7/20      125           宫崎骏 动画   9.2   日本

2.3.3 导入Excel数据

library("readxl") # 加载包
# 其中col_names参数仍然是为了设定是否把第一行当做变量名
movie_excel = data.frame(read_excel("./data/top250.xlsx", col_names = TRUE));head(movie_excel)
##   rank           name showtime duration         director type score nation
## 1    1   肖申克的救赎    34587      142 弗兰克·德拉邦特 剧情   9.6   美国
## 2    2       霸王别姬    33970      171           陈凯歌 爱情   9.5   中国
## 3    3 这个杀手不太冷    34591      110       吕克·贝松 动作   9.4   法国
## 4    4       阿甘正传    34508      142           Robert 爱情   9.4   美国
## 5    5       美丽人生    35784      116   罗伯托·贝尼尼 喜剧   9.5 意大利
## 6    6       千与千寻    37092      125           宫崎骏 动画   9.2   日本

2.3.4 逐行读入数据

在文件较大或格式较为复杂的情况下,直接将文件读入内存会花费很长时间。因此,可以每次读入一行文件,进行逐行处理。

#建立与文件的连接
con<-file("./data/top250.csv", encoding = "GBK")
#逐行读入所有数据
line_all<-readLines(con)
#读取前10行数据
line_10<-readLines(con,n=10)
line_10
##  [1] "rank,name,showtime,duration,director,type,score,nation"        
##  [2] "1,肖申克的救赎,1994/9/10,142,弗兰克·德拉邦特,剧情,9.6,美国"   
##  [3] "2,霸王别姬,1993/1/1,171,陈凯歌,爱情,9.5,中国"                  
##  [4] "3,这个杀手不太冷,1994/9/14,110,吕克·贝松,动作,9.4,法国"       
##  [5] "4,阿甘正传,1994/6/23,142,Robert,爱情,9.4,美国"                 
##  [6] "5,美丽人生,1997/12/20,116,罗伯托·贝尼尼,喜剧,9.5,意大利"      
##  [7] "6,千与千寻,2001/7/20,125,宫崎骏,动画,9.2,日本"                 
##  [8] "7,泰坦尼克号,1998/4/3,194,詹姆斯·卡梅隆,爱情,9.2,美国"        
##  [9] "8,辛德勒的名单,1993/11/30,195,史蒂文·斯皮尔伯格,历史,9.4,美国"
## [10] "9,盗梦空间,2010/9/1,148,克里斯托弗·诺兰,科幻,9.3,美国"
close(con)#关闭连接
split_line=strsplit(line_all,",")#分隔符为”,”
head(split_line,3)
## [[1]]
## [1] "rank"     "name"     "showtime" "duration" "director" "type"     "score"    "nation"  
## 
## [[2]]
## [1] "1"                "肖申克的救赎"     "1994/9/10"        "142"              "弗兰克·德拉邦特"
## [6] "剧情"             "9.6"              "美国"            
## 
## [[3]]
## [1] "2"        "霸王别姬" "1993/1/1" "171"      "陈凯歌"   "爱情"     "9.5"      "中国"

2.4 数据集管理及预处理

rm(list = ls())#清理工作空间
movie = read.csv("./data/top250.csv",fileEncoding = "gbk",stringsAsFactors = FALSE)#导入数据

2.4.1 了解数据概况

拿到数据集后需要先查看数据的概况,主要通过R语言中的汇总函数实现。首先使用str()函数查看每列数据的类型,了解取值情况。接下来,可通过summary()函数查看每列数据的汇总统计。

str(movie)
## 'data.frame':    250 obs. of  8 variables:
##  $ rank    : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ name    : chr  "肖申克的救赎" "霸王别姬" "这个杀手不太冷" "阿甘正传" ...
##  $ showtime: chr  "1994/9/10" "1993/1/1" "1994/9/14" "1994/6/23" ...
##  $ duration: int  142 171 110 142 116 125 194 195 148 98 ...
##  $ director: chr  "弗兰克·德拉邦特" "陈凯歌" "吕克·贝松" "Robert" ...
##  $ type    : chr  "剧情" "爱情" "动作" "爱情" ...
##  $ score   : num  9.6 9.5 9.4 9.4 9.5 9.2 9.2 9.4 9.3 9.3 ...
##  $ nation  : chr  "美国" "中国" "法国" "美国" ...
summary(movie)
##       rank           name             showtime            duration       director             type          
##  Min.   :  1.0   Length:250         Length:250         Min.   : 45.0   Length:250         Length:250        
##  1st Qu.: 60.5   Class :character   Class :character   1st Qu.:103.0   Class :character   Class :character  
##  Median :126.5   Mode  :character   Mode  :character   Median :118.0   Mode  :character   Mode  :character  
##  Mean   :125.9                                         Mean   :122.2                                        
##  3rd Qu.:190.0                                         3rd Qu.:136.0                                        
##  Max.   :250.0                                         Max.   :238.0                                        
##                                                        NA's   :12                                           
##      score           nation         
##  Min.   :-1.000   Length:250        
##  1st Qu.: 8.500   Class :character  
##  Median : 8.700   Mode  :character  
##  Mean   : 7.853                     
##  3rd Qu.: 8.900                     
##  Max.   : 9.600                     
## 

2.4.2 变量类型转换

1.基本数据类型之间的转换

变量类型转换可以分为两步,首先利用is族函数判断变量类型,再通过as族函数转换变量类型。

a="1"#将1赋值给a
is.numeric(a)#判断a是否是数值型数据
## [1] FALSE
a<-as.numeric(a)#将a转换为数值型数据
is.numeric(a)#再次判断a是否是数值型数据
## [1] TRUE

2.不同结构化数据类型间的转换

结构化数据类型之间的转换与基本数据类型转换相似,首先使用class()函数判断数据结构,再使用as族函数进行转换。

tbl <- table(movie$nation)#统计不同国家的频次
class(tbl)#查看数据格式
## [1] "table"
tbl <- as.data.frame(tbl)#转化为数据框
class(tbl)
## [1] "data.frame"

3.日期值转换

日期类型的变量需要使用如下方法进行转换。

###(1)将字符转换成Date日期格式###
# 函数head用来查看数据前6个元素,函数class用来查看对象数据类型
head(movie$showtime)
## [1] "1994/9/10"  "1993/1/1"   "1994/9/14"  "1994/6/23"  "1997/12/20" "2001/7/20"
class(movie$showtime)
## [1] "character"
movie$showtime = as.Date(movie$showtime)
head(movie$showtime)
## [1] "1994-09-10" "1993-01-01" "1994-09-14" "1994-06-23" "1997-12-20" "2001-07-20"
class(movie$showtime)
## [1] "Date"
as.Date('1/15/2020', format = '%m/%d/%Y') #对日/月/年类型字符进行日期转换
## [1] "2020-01-15"
###(2)将字符转换成POSIXct/POSIXlt时间格式###
as.POSIXct(1472562988, origin = "1960-01-01")#日期值转换,以"1960-01-01"为起点
## [1] "2006-08-30 21:16:28 CST"

2.4.3 时间型数据的操作

针对时间型数据的常用操作为:特征提取、差值运算、排序运算。

1.特征提取

library(lubridate)#加载lubridate包
t = "2020-11-20 01:30:29"
year(t)#提取年份
## [1] 2020
month(t)#提取月份
## [1] 11
mday(t)#提取日期是一个月中的第几天
## [1] 20
wday(t)#提取日期是一周中的第几天
## [1] 6
hour(t)#取出日期中的小时数
## [1] 1
minute(t)#取出日期中的分钟数
## [1] 30
second(t)#取出日期中的秒
## [1] 29

2.插值运算

# 求任意两个日期距离的天数
begin = as.Date("2016-03-04")
end = as.Date("2016-05-08")
(during = end - begin)
## Time difference of 65 days
# 求任意两个日期距离的周数和小时数
difftime(end, begin, units = "weeks")
## Time difference of 9.285714 weeks
difftime(end, begin, units = "hours")
## Time difference of 1560 hours

3.排序

# 单独对时间进行排序
head(sort(movie$showtime))
## [1] "1931-01-30" "1936-02-25" "1939-12-15" "1940-05-17" "1950-08-26" "1952-04-11"
# 对数据表格中的数据按照时间顺序排列,这里只选取前6行,对电影名称、上映日期做展示
head(movie[order(movie$showtime), c("name", "showtime")])
##         name   showtime
## 214 城市之光 1931-01-30
## 103 摩登时代 1936-02-25
## 20  乱世佳人 1939-12-15
## 147 魂断蓝桥 1940-05-17
## 171   罗生门 1950-08-26
## 157   雨中曲 1952-04-11

2.4.4 数据集合并

实践中,常常需要将不同表的信息进行合并。在R语言中可以使用函数merge()来实现数据框的合并。

director <- c("陈凯歌", "宫崎骏", "李廷香","詹姆斯·卡梅隆", "刘镇伟", "周星驰", "李安", "姜文", "张艺谋", "吴宇森","岩井俊二", "王家卫", "陈可辛"  )
birthyear <- c(1952,1941,1964,1954,1952,1962,1954,1963,1950,1946,1963,1958,1962)
gender <- c("男", "男", "女", "男", "男", "男", "男", "男", "男", "男", "男", "男", "男")
directors <- data.frame(director, birthyear, gender); 
# merge实现的效果是:将movie和directors按照列director匹配并合并起来
(movie.star = merge(movie[1:10, ], directors,by = "director")) 
##         director rank       name   showtime duration type score nation birthyear gender
## 1         陈凯歌    2   霸王别姬 1993-01-01      171 爱情   9.5   中国      1952     男
## 2         宫崎骏    6   千与千寻 2001-07-20      125 动画   9.2   日本      1941     男
## 3 詹姆斯·卡梅隆    7 泰坦尼克号 1998-04-03      194 爱情   9.2   美国      1954     男

2.4.5 数据缺失、异常

在R语言中检测缺失值可使用is.na()函数。该函数将会返回逻辑值TRUE或FALSE,TRUE代表缺失,FALSE代表未缺失。数据中的异常值为不符合常理或与总体取值高度不一致的数据。通常对数据是否缺失、异常的判断是在数据汇总之后进行的。

###(1)删除法###
movie_new=na.omit(movie)#保留完整观测的行

###(2)插补法###
#将均值替换电影时长缺失值
movie[is.na(movie$duration), ]$duration<-mean(movie$duration, na.rm = T)
movie[which(movie$score<0), ]$score<-NA#将异常值赋值为NA
movie[is.na(movie$score), ]$score<-mean(movie$score, na.rm = T)#赋值均值

习题答案

题目 2.1

如何理解R语言中的“向量化”操作?请举一个例子说明。

对于一个向量\(x\),“向量化”操作指命令可以直接对向量的每个元素进行操作,不需要使用循环实现计算。比如命令语句\(x\)+2或者\(x^3\),向量\(x\)中的每个元素都加2或每个元素都变成三次幂。

题目 2.2

请描述R语言中矩阵与数据框之间的两个不同点。

a.数据框实际上是由多个长度相同的向量组成,而矩阵实际上是一个二维向量。

b.数据框所包含的向量可以是不同数据类型的,而矩阵仅能包含一种数据类型。

题目 2.3

对矩阵的操作:

1.在R中生成生成下面的矩阵A。

\[A=\left(\begin{array}{lll} 1 & 2 & 3 \\ 4 & 2 & 1 \\ 2 & 3 & 0 \end{array}\right)\]

#构造矩阵
(A=matrix(c(1,4,2,2,2,3,3,1,0),nrow=3))
##      [,1] [,2] [,3]
## [1,]    1    2    3
## [2,]    4    2    1
## [3,]    2    3    0

2.计算矩阵A的转置矩阵B和逆矩阵C。

(B=t(A))#求转置矩阵
##      [,1] [,2] [,3]
## [1,]    1    4    2
## [2,]    2    2    3
## [3,]    3    1    0
(C=solve(A))#求逆矩阵
##       [,1]  [,2]  [,3]
## [1,] -0.12  0.36 -0.16
## [2,]  0.08 -0.24  0.44
## [3,]  0.32  0.04 -0.24

3.求矩阵A和矩阵C的乘积。

A%*%C#矩阵乘积
##              [,1] [,2] [,3]
## [1,] 1.000000e+00    0    0
## [2,] 5.551115e-17    1    0
## [3,] 0.000000e+00    0    1

题目 2.4(实训题目)

实训题目:使用电视剧网播量数据集,该数据集收集了4266条电视剧的信息。请完成以下任务。

  1. 获取数据集,查看数据概况。
tv<-read.csv("./data/电视剧播量.csv",fileEncoding="gbk",stringsAsFactors = F)#读取数据
head(tv)#查看数据前几行
##           剧名                                    类型 播放量    点赞    差评 得分           采集日期
## 1   花千骨2015            言情剧\n/\n穿越剧\n/\n网络剧 3.07亿 992,342 357,808  7.3 2015-9-23 23:48:48
## 2 还珠格格2015          古装剧\n\n/\n喜剧\n\n/\n网络剧 73.3万   2,352   7,240  2.5 2015-9-23 23:48:48
## 3         天局 武侠剧\n/\n古装剧\n/\n悬疑剧\n/\n网络剧 3454万  38,746   3,593  9.2 2015-9-23 23:48:52
## 4     明若晓溪            青春剧\n/\n言情剧\n/\n偶像剧 1.57亿 518,660  72,508  8.8 2015-9-23 23:48:51
## 5     多情江山            言情剧\n/\n古装剧\n/\n宫廷剧 1126万  22,553   6,955  7.6 2015-9-23 23:48:52
## 6   我是机器人                       偶像剧\n\n/\n喜剧 1660万  37,905   3,605  9.1 2015-9-23 23:48:51
  1. 删除数据集中剧名缺失的值。
tv<-tv[-which(tv$剧名 == "null"),]#删除缺失数据
  1. 不考虑缺失数据影响,计算电视剧的平均得分。
#将缺失数据处理成NA
tv[which(tv$得分=="null"),]$得分=NA
tv[which(tv$得分=="."),]$得分=NA
#转换数据格式
tv$得分<-as.numeric(tv$得分)
#计算平均得分
mean(tv$得分,na.rm = T)
## [1] 7.592556

题目 2.5 (实训题目)

实训题目:使用手机游戏数据集,该数据集收集了1141条手机游戏信息及评分。请完成以下任务。

  1. 获取数据集,查看数据概况。
game<-read.csv("./data/安卓手机游戏.csv",fileEncoding = "gbk",stringsAsFactors = F)#读取数据
summary(game)#查看数据概况
##    游戏名称              评分           类别               语言               热度           最后更新时间      
##  Length:1141        Min.   :1.000   Length:1141        Length:1141        Length:1141        Length:1141       
##  Class :character   1st Qu.:5.200   Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Median :6.600   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                     Mean   :6.611                                                                              
##                     3rd Qu.:7.900                                                                              
##                     Max.   :9.600                                                                              
##                     NA's   :203                                                                                
##    游戏版本             资费              开发商            支持系统             评论数          喜欢数       
##  Length:1141        Length:1141        Length:1141        Length:1141        Min.   :    0   Min.   :    0.0  
##  Class :character   Class :character   Class :character   Class :character   1st Qu.:  446   1st Qu.:   67.0  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :  859   Median :  233.0  
##                                                                              Mean   : 2322   Mean   :  786.3  
##                                                                              3rd Qu.: 2510   3rd Qu.:  854.8  
##                                                                              Max.   :96354   Max.   :13323.0  
##                                                                              NA's   :1       NA's   :1
  1. 提取热度中的数值部分,计算各游戏类型的热度均值,找出平均热度最高的游戏类型。
#加载包
library(stringr)
library(dplyr)
#转换数据格式
game$hot<-as.numeric(str_extract(game$热度,"\\d+"))
#计算热度均值
hot_aver<-game%>%
  group_by(类别)%>%
  summarise(meanhot=mean(hot,na.rm = T))#平均热度
#找出平均热度最高的游戏类型
(hot_aver=hot_aver[order(hot_aver$meanhot, decreasing = T), ])
## # A tibble: 16 x 2
##    类别       meanhot
##    <chr>        <dbl>
##  1 "体育运动"    46.0
##  2 "竞速游戏"    45.6
##  3 "游戏工具"    43.6
##  4 "动作游戏"    42.6
##  5 "养成游戏"    42.3
##  6 "角色扮演"    42.2
##  7 "益智休闲"    41.8
##  8 "飞行游戏"    41.6
##  9 "策略塔防"    40.9
## 10 "音乐游戏"    40.6
## 11 "格斗游戏"    40.6
## 12 "射击游戏"    40.0
## 13 "冒险解谜"    39.5
## 14 "模拟经营"    38.3
## 15 "棋牌游戏"    37.1
## 16 ""           NaN

平均热度最高的游戏类别为体育运动。

  1. 计算各游戏类型的平均评分,最高评分,最低评分,评分标准差,并作简要分析。
grade_aver<-game%>%
  group_by(类别)%>%
  summarise(meangrade=mean(评分,na.rm = T),#平均评分
            maxgrade=max(评分,na.rm = T),#最高评分
            mingrade=min(评分,na.rm = T),#最低评分
            sdgrade=sd(评分,na.rm = T))#评分标准差
(grade_aver=grade_aver[order(grade_aver$meangrade, decreasing = T), ])#按照平均得分排序
## # A tibble: 16 x 5
##    类别       meangrade maxgrade mingrade sdgrade
##    <chr>          <dbl>    <dbl>    <dbl>   <dbl>
##  1 "角色扮演"      7.00      9.1      4.6    1.33
##  2 "策略塔防"      6.88      9.3      4.8    1.39
##  3 "冒险解谜"      6.86      9        4.8    1.23
##  4 "体育运动"      6.84      8.8      4.9    1.46
##  5 "格斗游戏"      6.78      8.8      5      1.18
##  6 "模拟经营"      6.67      9.3      4.7    1.28
##  7 "动作游戏"      6.65      9        1      1.44
##  8 "射击游戏"      6.61      8.6      4.7    1.30
##  9 "养成游戏"      6.55      8.3      5      1.23
## 10 "益智休闲"      6.46      9.6      4.6    1.31
## 11 "音乐游戏"      6.4       9        5      1.34
## 12 "竞速游戏"      6.37      8.8      4.7    1.35
## 13 "棋牌游戏"      5.84      8.1      4.5    1.22
## 14 "飞行游戏"      5.70      8.2      4.7    1.09
## 15 "游戏工具"      5.03      6.4      1      1.63
## 16 ""            NaN      -Inf      Inf     NA

分析:虽然平均热度最高的游戏类别为体育运动,但平均评分最高的游戏类别为角色扮演,同时角色扮演的评分波动较大。