今天看啥  ›  专栏  ›  名本无名

R 数据处理(二十)—— apply

名本无名  · 简书  ·  · 2021-01-31 08:02

前言

在上一节中,我们主要介绍了 purrr 包提供的工具函数来减少 for 循环的使用,使代码更加的简洁,便于阅读。

但是,使用 R 原生的 apply 函数家族也能够极大减少 for 循环的使用。

下面我们主要介绍 apply 函数的使用。

apply 针对不同的数据类型,会有不同的变形,共同组成了 apply 函数家族。包括

apply , lapply , sapply , vapply , tapply , mapply , rapply , eapply

1. apply

1.1 描述

通过对数组或矩阵的 MARGIN 应用函数获得的向量、数组或数值列表

1.2 用法

apply(X, MARGIN, FUN, ...)

1.3 参数

  • X : 数组、矩阵、数据框,数据至少是二维的
  • MARGIN : 按行计算或按列计算, 1 表示按行, 2 表示按列
  • FUN : 自定义的调用函数
  • ... : FUN 的可选参数

1.4 示例

现在,我们有下面一个简单矩阵

> (my.matrx <- matrix(c(1:10, 11:20, 21:30), nrow = 10, ncol = 3))
      [,1] [,2] [,3]
 [1,]    1   11   21
 [2,]    2   12   22
 [3,]    3   13   23
 [4,]    4   14   24
 [5,]    5   15   25
 [6,]    6   16   26
 [7,]    7   17   27
 [8,]    8   18   28
 [9,]    9   19   29
[10,]   10   20   30

我们可以对每一行求和

> apply(my.matrx, 1, sum)
 [1] 33 36 39 42 45 48 51 54 57 60

计算每一列的长度

> apply(my.matrx, 2, length)
[1] 10 10 10

传递自定义匿名函数

> apply(my.matrx, 2, function (x) length(x)-1)
[1] 9 9 9

使用定义在外部的函数

> st.err <- function(x){
+     sd(x)/sqrt(length(x))
+ }
> apply(my.matrx,2, st.err)
[1] 0.9574271 0.9574271 0.9574271
转换数据

现在来点不一样的,在前面的示例中,我们使用 apply 对行或列应用函数。

其实,还可以对矩阵的单元格元素应用函数,如果您将 MARGIN 设置为 1:2 ,那么该函数将对每个单元格进行操作

> (my.matrx2 <- apply(my.matrx, 1:2, function(x) x+3))
      [,1] [,2] [,3]
 [1,]    4   14   24
 [2,]    5   15   25
 [3,]    6   16   26
 [4,]    7   17   27
 [5,]    8   18   28
 [6,]    9   19   29
 [7,]   10   20   30
 [8,]   11   21   31
 [9,]   12   22   32
[10,]   13   23   33

2. lappy

2.1 描述

将函数应用于输入变量的每一个元素,并返回与输入变量长度相同的 list

2.2 用法

apply(X, MARGIN, FUN, ...)

2.3 参数

  • X : list data.frame vector
  • FUN : 自定义的调用函数
  • ... : FUN 的可选参数

2.4 示例

假设有下面的变量

> (vec <- c(1:10))
 [1]  1  2  3  4  5  6  7  8  9 10

对每个元素求和

> str(lapply(vec, sum))
List of 10
 $ : int 1
 $ : int 2
 $ : int 3
 $ : int 4
 $ : int 5
 $ : int 6
 $ : int 7
 $ : int 8
 $ : int 9
 $ : int 10

返回一个长度为 10 list ,它并没有像我们期望的一样,返回一个 1-10 之和。

因为, lapply 会将向量视为列表,并将函数应用于每个元素上。

让我们将输入变为一个列表

> A<-c(1:9)
> B<-c(1:12)
> C<-c(1:15)
> my.lst<-list(A,B,C)
> lapply(my.lst, sum)
[[1]]
[1] 45

[[2]]
[1] 78

[[3]]
[1] 120

函数对列表中的每个向量求和,并返回一个长度为 3 的列表

如果输入的是一个矩阵

> (x <- matrix(1:12, nrow = 3))
     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12

> str(lapply(x, sum))
List of 12
 $ : int 1
 $ : int 2
 $ : int 3
 $ : int 4
 $ : int 5
 $ : int 6
 $ : int 7
 $ : int 8
 $ : int 9
 $ : int 10
 $ : int 11
 $ : int 12

lapply 会循环矩阵中的每个值,而不是按行或按列进行分组计算。

那如果对一个 data.frame 求和呢?

> str(lapply(as.data.frame(x), sum))
List of 4
 $ V1: int 6
 $ V2: int 15
 $ V3: int 24
 $ V4: int 33

可以看到, lapply 会自动对 data.frame 的列分组求和

3. sapply

3.1 描述

sapply 的工作原理和 lapply 一样,但是如果可能的话,它会简化输出。

这意味着,如果数据可以简化,它将返回一个向量,而不是像 lappy 那样总是返回一个列表。

3.2 用法

sapply(X, FUN, ..., simplify = TRUE, USE.NAMES = TRUE)

3.3 参数

  • X : 数组、矩阵、数据框
  • FUN : 自定义的调用函数
  • ... : FUN 的可选参数
  • simplify : 是否简化为向量、矩阵或高维数组,当值 array 时,输出结果按数组进行分组
  • USE.NAMES : 如果 X 为字符串且未设置名称,当该值为 TRUE 是设置字符串为数据名称, FALSE 不设置

3.4 示例

对向量使用 sapply 求和,返回一个长度相等的向量

> vec
 [1]  1  2  3  4  5  6  7  8  9 10
> sapply(vec, sum)
 [1]  1  2  3  4  5  6  7  8  9 10

对列表求和

> my.lst
[[1]]
[1] 1 2 3 4 5 6 7 8 9

[[2]]
 [1]  1  2  3  4  5  6  7  8  9 10 11 12

[[3]]
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

> sapply(my.lst, sum)
[1]  45  78 120

返回的还是一个向量

传入矩阵和 data.frame

> sapply(data.frame(x), sum)
X1 X2 X3 X4 
 6 15 24 33 
> sapply(x, sum)
 [1]  1  2  3  4  5  6  7  8  9 10 11 12

如果同时设置了 simplify=FALSE USE.NAMES=FALSE ,那么 sapply lapply 将是一样的

> str(sapply(data.frame(x), sum, simplify=FALSE, USE.NAMES=FALSE))
List of 4
 $ X1: int 6
 $ X2: int 15
 $ X3: int 24
 $ X4: int 33

使用 simplify = 'array' 构建一个三维数组

> (v <- structure(10*(5:7), names = LETTERS[1:3]))
 A  B  C 
50 60 70 
> sapply(v, function(x){outer(rep(x, 3), 2*(1:2))}, simplify = 'array')
, , A

     [,1] [,2]
[1,]  100  200
[2,]  100  200
[3,]  100  200

, , B

     [,1] [,2]
[1,]  120  240
[2,]  120  240
[3,]  120  240

, , C

     [,1] [,2]
[1,]  140  280
[2,]  140  280
[3,]  140  280

还可以对字符串向量自动生成数据名

> (val<-head(letters))
[1] "a" "b" "c" "d" "e" "f"
> sapply(val,paste,USE.NAMES=TRUE)
  a   b   c   d   e   f 
"a" "b" "c" "d" "e" "f" 
> sapply(val,paste,USE.NAMES=FALSE)
[1] "a" "b" "c" "d" "e" "f"

4. vapply

4.1 描述

类似于 sapply ,多了一个 FUN.VALUE 参数,用于指定返回值的行名

4.2 用法

vapply(X, FUN, FUN.VALUE, ..., USE.NAMES = TRUE)

4.3 参数

  • X : 数组、矩阵、数据框
  • FUN : 自定义的调用函数
  • FUN.VALUE : 定义函数返回值的模板
  • ... : FUN 的可选参数
  • USE.NAMES : 如果 X 为字符串且未设置名称,当该值为 TRUE 时设置字符串为数据名称, FALSE 不设置

4.4 示例

例如,对数据框求累积和,并设置返回值的模板,为返回值设置行名

> x
  V1 V2 V3 V4
1  1  4  7 10
2  2  5  8 11
3  3  6  9 12
> vapply(as.data.frame(x), cumsum, FUN.VALUE = c('a'=1, 'b'=1, 'c'=1))
  V1 V2 V3 V4
a  1  4  7 10
b  3  9 15 21
c  6 15 24 33

那如何从 lapply , sapply , vapply 这三个函数中进行选择呢?通常尽量使用 sapply ,如果不需要简化输出,则应该使用 lapply ,如果要指定输出类型,则使用 vapply

5. mapply:

5.1 描述

mapply 也是 sapply 的变形,类似多变量 sapply

5.2 用法

mapply(FUN, ..., MoreArgs = NULL, SIMPLIFY = TRUE,USE.NAMES = TRUE)

5.3 参数

  • FUN : 自定义的调用函数
  • ... : 接受多个数据
  • MoreArgs : 参数列表
  • simplify : 是否简化为向量、矩阵或高维数组,当值 array 时,输出结果按数组进行分组
  • USE.NAMES : 如果 X 为字符串且未设置名称,当该值为 TRUE 时设置字符串为数据名称, FALSE 不设置

5.4 示例

取三个向量对应位置的最大值

> x <- -4:5
> y <- seq(-9, 10, 2)
> z<-round(runif(10,-5,5))
> mapply(max,x,y,z)
 [1]  4  0 -1  3  0  2  4  5  7  9

6. tapply

6.1 描述

用于对数据进行分组应用函数,其中 INDEX 参数可以把数据集进行分组,相当于 group by 的操作。

6.2 用法

tapply(X, INDEX, FUN = NULL, ..., simplify = TRUE)

6.3 参数

  • X : 向量
  • INDEX : 用于分组的索引
  • FUN : 自定义的调用函数
  • ... : 接受多个数据
  • simplify : 是否简化为向量、矩阵或高维数组,当值 array 时,输出结果按数组进行分组

6.4 示例

首先,让我们创建一个因子数据作为索引

> (tdata <- as.data.frame(cbind(c(1,1,1,1,1,2,2,2,2,2), my.matrx)))
   V1 V2 V3 V4
1   1  1 11 21
2   1  2 12 22
3   1  3 13 23
4   1  4 14 24
5   1  5 15 25
6   2  6 16 26
7   2  7 17 27
8   2  8 18 28
9   2  9 19 29
10  2 10 20 30
> colnames(tdata)
[1] "V1" "V2" "V3" "V4"

然后把第 1 列作为索引,并计算第 2 列的均值

> tapply(tdata$V2, tdata$V1, mean)
1 2 
3 8 

7. rapply

7.1 描述

rapply 是一个递归版本的 lapply ,只针对 list 类型的数据,对 list 的每个元素进行递归遍历,如果 list 的子元素还包含数据则继续遍历。

7.2 用法

rapply(object, f, classes = "ANY", deflt = NULL, how = c("unlist", "replace", "list"), ...)

7.3 参数

  • object : list 类型数据

  • f : 自定义函数

  • classes : 匹配类型, ANY 为所有类型

  • deflt : 非匹配类型的默认值

  • how : 包含 3 种方式:

    • 如果为 replace ,则用调用函数 f 后的结果来替换原 list 中的元素;
    • 当为 list 时,创建一个新的 list 并调用 f 函数,不匹配赋值为 deflt
    • 当为 unlist 时,会执行一次 unlist(recursive = TRUE) 的操作
  • ... : 可选参数

7.4 示例

假设我们有如下列表

> l
$x
$x$x
[1] 1 2 3 4 5

$x$y
[1] "a" "b" "c" "d" "e" "f" "g"


$y
[1]  7  8  9 10 11 12 13

$z
[1] "t" "u" "v" "w" "x" "y" "z"

> str(l)
List of 3
 $ x:List of 2
  ..$ x: int [1:5] 1 2 3 4 5
  ..$ y: chr [1:7] "a" "b" "c" "d" ...
 $ y: int [1:7] 7 8 9 10 11 12 13
 $ z: chr [1:7] "t" "u" "v" "w" ...

我们想对数字进行降序排序

> rapply(l, function(x) {sort(x, decreasing = TRUE)}, classes='integer',how='replace')
$x
$x$x
[1] 5 4 3 2 1

$x$y
[1] "a" "b" "c" "d" "e" "f" "g"


$y
[1] 13 12 11 10  9  8  7

$z
[1] "t" "u" "v" "w" "x" "y" "z"

我们为字符串添加星号,并将非字符串负值为 NA

> rapply(l, function(x) paste('*', x, '*'), classes='character',how='list', deflt = NA)
$x
$x$x
[1] NA

$x$y
[1] "* a *" "* b *" "* c *" "* d *" "* e *" "* f *" "* g *"


$y
[1] NA

$z
[1] "* t *" "* u *" "* v *" "* w *" "* x *" "* y *" "* z *"

8. eapply

8.1 描述

将函数应用于环境中的命名变量中,并将结果作为列表返回。

用户可以请求使用所有命名对象(通常以点开头的名称不被使用),不会对输出进行排序,也不会搜索封闭环境

8.2 用法

eapply(env, FUN, ..., all.names = FALSE, USE.NAMES = TRUE)

8.3 参数

  • env : 使用的环境空间
  • FUN : 自定义函数
  • ... : FUN 的可选参数
  • all.names : 指示是否将函数应用于所有值
  • USE.NAMES : 如果 X 为字符串且未设置名称,当该值为 TRUE 时设置字符串为数据名称, FALSE 不设置

8.4 示例

我们新建一个环境空间,然后在空间内新建 3 个对象,最后用 eapply 对所有环境空间内的变量求均值

# 新建一个环境空间
> env <- new.env(hash = FALSE) 
# 为环境空间添加变量
> env$a <- 1:10
> env$beta <- exp(-3:3)
> env$logic <- c(TRUE, FALSE, FALSE, TRUE)
# 对 env 环境空间的所有变量求均值
> eapply(env, mean)
$logic
[1] 0.5

$beta
[1] 4.535125

$a
[1] 5.5

我们可以使用 ls() 函数获取环境空间内的所有变量或对象

# 获取当前环境空间内的变量或对象
> ls() %>% head()
[1] "a"     "A"     "a2"    "args1" "args2" "B"

# 通过传入环境空间,获取该环境空间内的变量
> ls(env)
[1] "a"     "beta"  "logic"

获取环境空间变量占用内存大小

> eapply(env, object.size)
$logic
64 bytes

$beta
112 bytes

$a
96 bytes

一般你很少用到 eapply ,但是对于 R 包开发者来说,环境空间是很重要的,需要掌握




原文地址:访问原文地址
快照地址: 访问文章快照