在介绍数据整合和整形操作之前,先介绍取代传统数据框的Tibble对象,在原有数据框的基础上做了改进,使得后面处理更加方便。
Tibble 对象
Tibble与传统的data.frame不同的地方主要有:1. tibble函数不会将字符型变量转换为因子变量;2. tibble函数不会自行改变变量的名字,不管这个名字是否符合R对变量名的要求(比如必须要用字母开头,不能有空格等)
大部分R包还在使用传统的data.frame
new_data <- as.tibble(iris)
你也可以自己创造tibble对象
tibb <- tibble (
x = 1:3
y = 1 #tibble 会自动补齐1
z = x ^2 + y
)
>tibb
# A tibble: 3 x 3
x y z
<int> <dbl> <dbl>
1 1 1 2
2 2 1 5
3 3 1 10
# 创建非法变量名 (要用` `包含这些变量名,如果要在其他包中使用这些变量名也需要单引号)
tb <- tibble (
`:)` = "smile",
` ` = "space",
`2000` = "number"
)
> tb
# A tibble: 1 x 3
`:)` ` ` `2000`
<chr> <chr> <chr>
1 smile space number
Tibble VS data.frame
在用法上有两点不同:printing 和subsetting
# 对于large tibble,输出时只输出前10行,输出适合屏幕的列,并且标注每一列变量的类型
tb <- tibble(
a = lubridate::now() + runif(1e3) * 86400,
b = lubridate::today() + runif(1e3) * 30,
c = 1:1e3,
d = runif(1e3),
e = sample(letters, 1e3, replace = TRUE)
)
> tb
# A tibble: 1,000 x 5
a b c d e
<dttm> <date> <int> <dbl> <chr>
1 2019-06-06 12:40:21 2019-06-19 1 0.212 b
2 2019-06-06 03:17:57 2019-06-20 2 0.497 x
3 2019-06-06 13:54:52 2019-06-28 3 0.423 n
4 2019-06-06 10:35:46 2019-07-02 4 0.0155 s
5 2019-06-06 11:14:17 2019-06-12 5 0.0417 o
6 2019-06-06 06:54:12 2019-06-29 6 0.783 s
7 2019-06-06 17:58:54 2019-06-06 7 0.560 l
8 2019-06-06 15:35:44 2019-06-24 8 0.458 h
9 2019-06-06 16:16:42 2019-07-03 9 0.972 f
10 2019-06-06 02:48:29 2019-06-27 10 0.991 q
# … with 990 more rows
#传统数据框提取单个变量,可以使用 $ 和 [[ , [[ 可以根据变量名和变量索引来提取,$只能通过变量名提取。
#在tibble对象这两个方法仍然有效
df <- tibble(
x = runif(5),
y = rnorm(5)
)
df$x
#> [1] 0.434 0.395 0.548 0.762 0.254
df[[1]]
#> [1] 0.434 0.395 0.548 0.762 0.254
# 在管道操作符中提取变量可以使用占位符" . " 来表示管道符前面的数据
df %>% .[["x"]]
数据整合
R基础包中有几个强大的函数,apply()、lapply()和sapply等,他们做的事情类似,只是对应的对象或返回的格式不同。它们主要是依次对某一对象的某一部分重复应用一个指定的函数。今天先不介绍apply家族的一些函数,重点介绍dplyr这个包。
数据框显示
# tbl_df() 函数: 能将数据转换为tbl类,这样看起来更加方便,输出会调整适应窗口
df %>% tbl_df()
>df
# A tibble: 5 x 2
x y
<dbl> <dbl>
1 0.883 0.960
2 0.686 -0.550
3 0.294 -1.78
4 0.220 1.12
5 0.0805 -0.247
# glimpse()函数:类似之前的tbl_df()函数,只是转了方向。变量由列变成行。输出结果同样可以自动调整以适应窗口
> df %>% glimpse()
Observations: 5
Variables: 2
$ x <dbl> 0.88283076, 0.68555417, 0.29363176, 0.21987971, 0.08045184
$ y <dbl> 0.9601924, -0.5497421, -1.7762194, 1.1158697, -0.2467130
数据截选
按行(观测值)选取 filter()
比较操作符
R提供比较操作符让你选取所需要的行,常见的有:> , >= , < , <= , != , ==
逻辑操作符
三个逻辑操作符 &,|,!,可以运用在filter()中实现多条件筛选
jan1 <- filter(flights, month == 1, day == 1)
filter(flights, month == 11 | month == 12)
按列(变量)截选 select()
# Select columns by name
select(flights, year, month, day)
# 用冒号选取两变量间所有变量
select(flights, year:day)
# 用负号表示不选取该变量
select(flights, -(year:day))
另外dplyr包中提供了一些实用的函数可以运用在select()中
start_with("abc") : match names that begin with “abc”
ends_with("xyz"): matches names that end with “xyz”
contains("ijk"): matches names that contain “ijk”
num_range("x", 1:3): matches x1, x2 and x3
select() 也可用对变量进行重命名,但一般很少用。因为它会舍弃其他没指定的变量,推荐使用 rename()
df %>% select("a" = x)
#> df %>% select("a" = x)
a
1 0.88283076
2 0.68555417
3 0.29363176
4 0.21987971
5 0.08045184
df %>% rename("a" = x)
#> df %>% rename("a" = x)
a y
1 0.88283076 0.9601924
2 0.68555417 -0.5497421
3 0.29363176 -1.7762194
4 0.21987971 1.1158697
5 0.08045184 -0.2467130
对于select还想多说一点,如果数据中有很多变量,你想移动一些变量至列的开头,这时候 everything() 就能派上大用场
select(flights, time_hour, air_time, everything())
# everything() 指的是剩余所有变量,避免了手动输入
对数据的行排序
arrange() 能对选取的变量的观测值进行排序,若对多个变量排序,则排在前面的变量优先度高
flights %>% arrange (year)
flights %>% arrange (year, month, day)
#默认从小到大排序,可使用desc() 进行从大到小排序
flights %>% arrange (desc(year))
增加新的变量
除了选择变量外,根据已知变量新建变量则很有用,这就要用到 mutate()。
有用的创造函数:
1.算术操作符: + , - , * , / , ^ (这些都是向量化函数,自动补齐短的参数)
2.模数运算符: %/%(取整除法), %%(取余除法)
3.Log: log(), log2(), 对数操作符可以对跨多个数量级的数据进行转化,推荐使用log2(), +1表示倍增,-1表示减少1/2
4.累计操作符: cunsum, cumprob, cummin,cummax,dplyr还提供一个cummean
5.逻辑比较: < , <= , > , >= , != , ==
6.求秩函数: 默认从最小值到最大值求秩,若要从最大值开始需要先对变量
desc(),min_rank(最通用的,最小的数定义最小的秩),还可以使用row_number(),不同点是row_number()相同的数值不会标注相同的秩
df %>% mutate(z = x + y)
> df %>% mutate(z = x + y)
x y z
1 0.88283076 0.9601924 1.8430231
2 0.68555417 -0.5497421 0.1358121
3 0.29363176 -1.7762194 -1.4825876
4 0.21987971 1.1158697 1.3357494
5 0.08045184 -0.2467130 -0.1662611
#如果只想保留新生成的变量用transmute()
df %>% transmute(z = x + y)
> df %>% transmute(z = x + y)
z
1 1.8430231
2 0.1358121
3 -1.4825876
4 1.3357494
5 -0.1662611
分类汇总
最后一个重要的函数是summarise(),将数据框变成单行
有用的汇总函数:
1.Measures of Location: mean() , median()
2.Measures of spread: sd() , IQR() , mad() 检查异常值
3.Measures of rank: min() , max() , quantile(x, 0.25)--找到一个值大于所有数据的25%,小于其他的75%
4.Measures of position: first()--首个元素,nth(x,n)--x中的第n个元素,last()--最后元素
5.count: n()--不用输入任何参数,返回每个组的观测数量,sum(is.na(x))--统计非缺失数据,n_distinct(x)--统计非重复值数量
6.Counts and proportions of logical values: sum(x>0)--统计多少为TRUE
df %>% summarise(mean(x))
> df %>% summarise(mean(x))
mean(x)
1 0.4324696
上面这段代码还没能体现summarise() 的设计初衷,其与group_by() 搭配使用就能发挥很好的作用,这样分析的目标就不是整个数据集,而是group_by() 将数据划分的一些group,对于每一个group,可以运用一些整合函数进行分类汇总
df
> df
x y name
1 0.88283076 0.9601924 a
2 0.68555417 -0.5497421 b
3 0.29363176 -1.7762194 a
4 0.21987971 1.1158697 c
5 0.08045184 -0.2467130 b
df %>% group_by(name) %>% summarise(mean = mean(y))
# A tibble: 3 x 2
name mean
<chr> <dbl>
1 a -0.408
2 b -0.398
3 c 1.12
# 分类计数 n()函数
df %>% group_by(name) %>% summarise(n = n())
# A tibble: 3 x 2
name n
<chr> <int>
1 a 2
2 b 2
3 c 1
#介绍一个重要的函数count(如果只是单纯想计算频数可以省略 group_by() + summarise() )
df %>% count(name)
# A tibble: 3 x 2
name n
<chr> <int>
1 a 2
2 b 2
3 c 1
等价于 df %>% group_by(name) %>% summarise(n = n())
#添加wt参数,一步实现分类然后对每个组的某个变量加和
df %>% count(name,wt = x)
# A tibble: 3 x 2
name n
<chr> <dbl>
1 a 1.18
2 b 0.766
3 c 0.220
等价于 df %>% group_by(name) %>% summarise(sum = sum(x))
#取消分组
df %>% ungroup()
分组mutates和filters
虽然group_by() 和summarise() 是绝配,但是它和mutates() , filter()搭配在一起也很方便
df %>% group_by(name) %>% mutate(z = x / sum(x))
# A tibble: 5 x 5
# Groups: name [3]
x y name sex z
<dbl> <dbl> <chr> <chr> <dbl>
1 0.883 0.960 a b 0.750
2 0.686 -0.550 b g 0.895
3 0.294 -1.78 a b 0.250
4 0.220 1.12 c g 1
5 0.0805 -0.247 b b 0.105
df %>% group_by(name) %>% filter(n() >1)
其他实用的函数
distinct() : 删除数据框中重复的行
sample_frac() : 随机选取一定比例的行
sample_n() : 随机选取一定数目的行
slice() : 选取指定位置的行, slice(10:15)和 df[10:15,]类似
top_n() : 选取某变量取值最高的若干观测。如果有指定组的话,可以对每组选择相应变量最高的观测
df %>% group_by(name) %>% summarise(max(x))
# A tibble: 3 x 2
name `max(x)`
<chr> <dbl>
1 a 0.883
2 b 0.686
3 c 0.220
df %>% group_by(name) %>% top_n(1,x) # 选取最高的观测
# A tibble: 3 x 4
# Groups: name [3]
x y name sex
<dbl> <dbl> <chr> <chr>
1 0.883 0.960 a b
2 0.686 -0.550 b g
3 0.220 1.12 c g
df %>% group_by(name) %>% top_n(2,x) #选取前两个观测 top_n(n,x)
# 可以看出top_n() 相比summarise(max())更加实现
补充内容
泛函: 以函数作为输入并返回一个向量的函数,常用的功能之一就是替代循环
lapply() , apply() 和 tapply()。它们都可以接收一个函数作为输入,并返回一个向量作为输出
第一个泛函 lapply()
最简单的泛函就是lapply。lapply()接收一个函数,并将这个函数应用到列表中的每一个元素,最后将结果以列表的形式返回。
#利用lapply对flights数据中每一个变量判断是否为数值型
lapply(flights,is.numeric)
#lapply实际上封装了for循环(但是不如lapply高效)
out <- vector("list",length = length(flights))
for (i in names(flights)) {
out[[i]] <- is.numeric(flights[[i]])
}
循环模式
有3种基本方法可以对一个向量进行循环操作
1)对每一个元素进行循环: for (x in xs)
2)根据元素的数值索引进行循环: for (i in seq_along(xs))
3)根据元素的名字进行循环: for (nm in names(xs) )
最好先为输出创建一个空向量(空间),然后再向其中添加元素,所以第二种方法是最简单的方法
res <- numeric (length(xs))
for (i in seq_along(xs) ) {
res[i] <- sqrt(xs[i])
}
正如使用for循环有3种方法,使用lapply()也有3种方法
1)lapply(xs,function(x) {} )
2)lapply(seq_along(xs), function(i) {} )
3)lapply(names(xs), function(nm) {} )
通常情况下,选择第一种方法,但是如果需要知道元素的位置或名字,就应该选择第二种或第三种方法
向量输出:sapply和vapply
sapply()和vapply()与lapply() 非常相似,除了它们输出的是原子向量外,sapply()是通过猜测来设定输出的类型,而vapply()是通过一个附加参数来设定输出类型
对于一个给定的数据框,sapply()和vapply()返回相同的结果。如果函数返回不同类型或者不同长度的结果,sapply()就会静静地返回一个列表,而vapply()会抛出一个错误
df <- data.frame(x = 1:10,y = Sys.time() + 1:10)
sapply(df,class)
#> sapply(df,class)
$x
[1] "integer"
$y
[1] "POSIXct" "POSIXt"
vapply(df,class,character(1))
Error in vapply(df, class, character(1)) : 值的长度必需为1,
但FUN(X[[2]])结果的长度却是2
因此,sapply是对lapply的一个简单包装,使得结果简化成向量,而vapply相当于是对函数返回结果通过FUN.VALUE参数进行验证
多重输入
lapply()中只有一个参数是可以改变的,其他参数都是固定的。这非常不适合解决某些问题。例如,当你有两个列表,一个列表是观测值,另一个列表是权重值时,如何计算他们的加权平均值
不能使用lapply(x,means,w),因为给lapply()提供的附加参数是传送给每个调用的
xs <- replicate(5,runif(10),simplify = F)
ws <- replicate(5,rpois(10,5) + 1,simplify = F)
# 使用lapply()计算加权平均数
lapply(seq_along(xs),function(x) weighted.mean(xs[[x]],ws[[x]]))
# 对于上面看着有些繁琐,因此有个替代的函数Map
Map(weighted.mean,xs,ws)
因此,当我们对多个列表或数据框同时处理时,Map很有用。
如果有些参数应该是固定的或者是常量,那么可以使用匿名函数
Map(function(x,w) weighted.mean(x,w,na.rm = TRUE),xs,ws)
并行化
实现lapply()时很有意思的一点是,由于每一次迭代都独立于其他迭代,所以迭代的顺序是不重要的
lapply2 <- function(x,f, ...) {
out <- vector("list",length(x))
for (i in sample(seq_along(x))) {
out[[i]] <- f(x[[i]], ...)
}
out
}
unlist(lapply(1:10,sqrt))
unlist(lapply2(1:10,sqrt))
#计算结果一样
这就产生了一个非常重要的结果:因为可以按人以顺序进行计算,所以可将这些计算任务分配给多个CPU从而实现并行计算
# 以生成线性模型的自助法重复个案为例
library(parallel)
boot_df <- function(x) x[sample(nrow(x),replace = T),]
rsquard <- function(mod) summary(mod)$r.square
boot_lm <- function(i) {
rsquard(lm(mpg~wt+disp,data = boot_df(mtcars)))
}
system.time(lapply(1:500,boot_lm))
system.time(mclapply(1:500,boot_lm,mc.cores = 2))
> system.time(lapply(1:500,boot_lm))
用户 系统 流逝
0.658 0.085 0.742
> system.time(mclapply(1:500,boot_lm,mc.cores = 2))
用户 系统 流逝
0.000 0.015 0.440
操作矩阵和数据框
apply
到目前为止,我们见过的所有泛函都处理一维输入结构。apply()是sapply()的变体,它可以处理矩阵和数组。
# 1=行,2=列
mat <- matrix(1:10,nrow = 2)
apply(mat,1,sum)
apply(mat,2,sum)
sweep
sweep()可以对统计汇总的值进行扫描,通常和apply()一起使用对数组进行标准化
x <- matrix(rnorm(20,0,10),nrow = 4)
x1 <- sweep(x,1,apply(x,1,min),`-`)
x2 <- sweep(x1,1,apply(x1,1,max),`/`)
outer
outer用来求向量的外积,可以接收多个向量输入并创建一个矩阵或数组输出
#向量外积可以看成列向量*行向量(列向量中的每一个元素对行向量进行伸缩变换)
outer(1:3,1:10,`*`)
组应用
可以把tapply()想象成apply()的一般化,当代数据集进行汇总时经常需要使用这个函数
pulse <- round(rnorm(22,70,10 / 3) + rep (c(0,5),c(10,12)))
group <- rep(c("a","b"),c(10,12))
tapply(pulse,group,length)
tapply(pulse,group,mean)
tapply()首先根据一组输入创建一个不规则的数据结构,然后将函数应用于这个数据结构中的每一个元素。第一步实际上是由split()函数来完成。它接受两个输入并根据第二个向量对第一个向量进行分组,最后将已分组的结果以一个列表的形式返回
split(pulse,group)
> split(pulse,group)
$a
[1] 73 68 68 73 65 78 64 67 71 65
$b
[1] 70 86 78 71 77 71 80 72 78 79 72 77
# tapply实际上就是split()和sapply()的组合
tapply2 <- function(x,group,f, ... ,simplify = TRUE) {
pieces <- split (x,group)
sapply(pieces,f,simplify = simplify)
}
列表操作
还可以把泛函看作对列表进行:改变、选取子集和汇总的常用工具集。每个函数式编程语言都有3个工具来完成这些任务:Map()、Reduce()和Filter()。
Reduce
Reduce通过递归调用一个函数f,每次有两个参数,将一个向量x简化为一个值
Reduce(`+`,1:3) #( (1+2) + 3 )
Reduce()可以将只能处理两个输入的函数扩展为一个可以处理任意多个输入的函数,它对实现多种类型的递归操作很有用
判断泛函
判断就是只能返回TRUE或者FALSE的函数,如is.character,is.NULL。判断泛函就是对一个列表或者数据框中的每一个元素进行判断,基础包中有3个很有用的判断泛函: Filter()、Find()、Position()。
Filter()只选择满足判断条件的元素
Find()返回满足判断条件的第一个
Position()返回满足判断条件的第一个元素的位置
df <- data.frame(x = 1:3,y = c("a","b","c"))
Filter(is.factor,df)
Find(is.factor,df)
base::Position(is.factor,df)