美文网首页
83.关于purrr包中的函数与apply函数的运行原理说明-十

83.关于purrr包中的函数与apply函数的运行原理说明-十

作者: 心惊梦醒 | 来源:发表于2021-11-01 12:09 被阅读0次

【上一篇:82.关于使用for循环时的四种问题及解决方案】
【下一篇:84.关于apply族的lapply/sapply/vapply】

    For循环在R中不像在其他语言中那么重要,因为R是一种函数式编程语言。这意味着可以在函数中封装for循环,并调用该函数而不是直接使用for循环。
    purrr包提供了很多函数,用来消除对for循环的需要。Base R的apply族函数有类似的功能,但purrr包中的函数更有一致性,也更容易学习。使用purrr函数的目标是允许你将常见的列表操作挑战分解成小部分:1)当你知道如何对list中的一个元素进行操作,purrr函数会自动将相同的操作推广至列表中的每一个元素。2)purrr还可以将复杂问题分解成小部分,每个部分分别解决,可以通过管道符一步一步向最终解决问题迈进。
    purrr函数下篇再写,先写写一直以来怯怯地不愿意学习的apply族函数,看一次记不住,学一次还是不懂,以至于直到现在我还在各种for循环。本篇一定要把这些函数弄懂记住会应用。首先从apply开始.....

apply函数

    Apply Functions Over Array Margins

apply(X, MARGIN, FUN, ..., simplify = TRUE)
输入参数

    X:输入,array或者matrix。如果输入不是array和matrix,而是一个有着non-null dim值的对象,apply将用as.matrix(适用二维对象,例如data.fame)as.array()将其强制转换成array。
    MARGIN:一个向量,指定FUN函数将被应用到X的哪个维度上。可以是整数:1=行,2=列,c(1,2)=行和列;可以是字符串向量,前提是输入对象X必须有named dimnames(这种情况更多地适用于超过2维的数组)。
    FUN: 函数,可以是自定义函数;也可以是环境变量种的函数;也可以是符号(例如:+,%*%),这种情况下,符号必须是反引用的name。
    ...:传递给FUN的可选参数。这里面的参数不能和apply函数的其他参数有相同的名字,也要注意尽可能避免与MARGIN或FUN部分匹配如果...中有参数,可以显式写明前三个参数的参数名
    simplify:逻辑值,指示是否结果应该尽可能简化,默认是TRUE。具体在下面的举例说明和总结中详细写。

输出值

    Returns a vector or array or list of values obtained by applying a function to margins of an array or matrix.
    1)如果每次调用FUN都返回一个长度为n的向量,当n>1时,apply()最终将返回一个维度为c(n, dim(X)[MARGIN])的数组;如果n=1,如果length(MARGIN)=1,apply()最终将返回一个向量,如果length(MARGIN)>1,则将返回一个维度为dim(X)[MARGIN]的数组。
    2)如果每次调用FUN返回的向量长度不同,apply()将返回一个长度为prod(dim(X)[MARGIN])的list,如果列表长度>1,dim设置为MARGIN。
    3)在所有情况下,在维度被设置之前,结果将被用as.vector()强制转换成一种基本的向量类型,因此因子将被转换成字符向量。

举例说明和总结

    1)输入对象类型是array或matrix,之所以可以应用到数据框上是因为数据框可以被强制转成array。apply()函数是for循环的替代,所以其工作原理是(对二维对象):确定在输入对象的哪个维度上操作(按行还是按列)后,循环调用FUN dim(X)[MARGIN]次,每次调用FUN后返回的结果都存到一个list中(simplify=F的时候),最后根据每次返回结果的长度以及设置的simplify参数输出最终的结果。
    先创建一个数组用于举例说明:

# 创建一个3 x 8的数组
> (x<-array(1:24,c(3,8)))
     [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,]    1    4    7   10   13   16   19   22
[2,]    2    5    8   11   14   17   20   23
[3,]    3    6    9   12   15   18   21   24
# 给数组的各个维度命名,1=行,2=列,名字以列表的形式传递进去
> (dimnames(x)<-list(letters[1:3],LETTERS[1:8]))
[[1]]
[1] "a" "b" "c"

[[2]]
[1] "A" "B" "C" "D" "E" "F" "G" "H"
# 得到命名后的数组
> x
  A B C  D  E  F  G  H
a 1 4 7 10 13 16 19 22
b 2 5 8 11 14 17 20 23
c 3 6 9 12 15 18 21 24

    2)类似sum(求和)、mean(求平均值)、sd(求标准差)这类的函数。假如在x的"列"上调用sum()函数:每次调用只返回一个值(长度为1的向量),这个向量会被先存到list中。如果simplify=F,直接返回list,反之返回一个as.vector(list)之后的向量。这种思路和82.关于使用for循环时的四种问题及解决方案中"未知长度的输出"一节里描述的一致。

# List比atomic vector的结构复杂。list长度为8,与x的列数一致
> apply(x,2,sum,simplify = F)
$A
[1] 6

$B
[1] 15

$C
[1] 24

$D
[1] 33

$E
[1] 42

$F
[1] 51

$G
[1] 60

$H
[1] 69
# 返回长度为dim(x)[2]=8的向量
> apply(x,2,sum,simplify = T)
 A  B  C  D  E  F  G  H 
 6 15 24 33 42 51 60 69 

    3)类似sort(排序)、quantile(求分位数)、fivenum(求输入数字的五个汇总结果:minimum, lower-hinge, median, upper-hinge, maximum)这类的函数。每次调用将返回长度>1的向量,因此最终将返回维度为c(n,dim(x)[MARGIN])的数组。即最终array的列数由dim(x)[MARGIN]决定,行数由每次调用返回向量的长度决定。

> x
  A B C  D  E  F  G  H
a 1 4 7 10 13 16 19 22
b 2 5 8 11 14 17 20 23
c 3 6 9 12 15 18 21 24
# 在x的"行"上调用sort函数,每次返回排序后的结果存到List中
> apply(x,1,sort,simplify = F)
$a
 A  B  C  D  E  F  G  H 
 1  4  7 10 13 16 19 22 

$b
 A  B  C  D  E  F  G  H 
 2  5  8 11 14 17 20 23 

$c
 A  B  C  D  E  F  G  H 
 3  6  9 12 15 18 21 24 
# 列表中有几个元素,最终返回结果中就有几列,行数由每次调用返回向量的长度决定
> apply(x,1,sort,simplify = T)
   a  b  c
A  1  2  3
B  4  5  6
C  7  8  9
D 10 11 12
E 13 14 15
F 16 17 18
G 19 20 21
H 22 23 24
# 在"行"上求分位数,因为x有3行,所以结果有3列
> apply(x,1,quantile,simplify = T)
         a     b     c
0%    1.00  2.00  3.00
25%   6.25  7.25  8.25
50%  11.50 12.50 13.50
75%  16.75 17.75 18.75
100% 22.00 23.00 24.00
# 在"列"上求分位数,因为x有8列,所以最终结果有8列
> apply(x,2,quantile,simplify = T)
       A   B   C    D    E    F    G    H
0%   1.0 4.0 7.0 10.0 13.0 16.0 19.0 22.0
25%  1.5 4.5 7.5 10.5 13.5 16.5 19.5 22.5
50%  2.0 5.0 8.0 11.0 14.0 17.0 20.0 23.0
75%  2.5 5.5 8.5 11.5 14.5 17.5 20.5 23.5
100% 3.0 6.0 9.0 12.0 15.0 18.0 21.0 24.0

    3)自己编写一个函数用于每次调用输出不同长度的向量。每次调用FUN后返回向量的长度不一,最终返回一个长度为prod(dim(X)[MARGIN])的list,如果列表长度>1,dim设置为MARGIN。

# 自定义randomfun函数
> randomfun <- function(x){num<-sample(x,1); rnorm(num)}
# 最终返回长度为3的列表。
> apply(x,1,randomfun)
$a
 [1]  0.021588044  0.154044569  1.007243515  0.721791760
 [5] -0.002292179 -1.247562257 -0.127432402  1.590593188
 [9] -0.321024620 -1.058287397

$b
 [1] -0.52756211  1.78941808 -0.76817645  0.64014366 -0.81748389
 [6] -0.11201013  0.24503978  0.74625223  0.33750790  0.83325951
[11]  1.91125363 -2.64110347  1.28993138  1.72833004 -0.56131632
[16] -0.18110910 -0.21715156 -0.33053400  1.24114389 -0.07563756

$c
[1] -1.628538306  0.923867726 -1.128612284  1.804218194
[5]  0.009244434 -0.456690775  1.669793035  0.420872252
[9] -0.454415607

    4)传递给FUN的参数不为空的例子:

# x对象的构建和这里的例子见apply()函数的帮助文档即可
> x
   col
row x1 x2
  a  3  4
  b  3  3
  c  3  2
  d  3  1
  e  3  2
  f  3  3
  g  3  4
  h  3  5
# 自定义函数cave:求x对象每行的x1列的平均值和x1、x2的平均值
> cave <- function(x, c1, c2) c(mean(x[c1]), mean(x[c2]))
# x有8行,所以最终结果有8列;cave函数返回一个长度为2的向量,所以最终结果有2行:第一行表示mean(x1),第二行为mean(x1,x2)
> apply(x, 1, cave,  c1 = "x1", c2 = c("x1","x2"))
      row
         a b   c d   e f   g h
  [1,] 3.0 3 3.0 3 3.0 3 3.0 3
  [2,] 3.5 3 2.5 2 2.5 3 3.5 4

    5)当MARGIN=c(1,2)的时候,计算方法是?

> apply(x,c(1,2),quantile,simplify = T)
, , col = x1

      row
       a b c d e f g h
  0%   3 3 3 3 3 3 3 3
  25%  3 3 3 3 3 3 3 3
  50%  3 3 3 3 3 3 3 3
  75%  3 3 3 3 3 3 3 3
  100% 3 3 3 3 3 3 3 3

, , col = x2

      row
       a b c d e f g h
  0%   4 3 2 1 2 3 4 5
  25%  4 3 2 1 2 3 4 5
  50%  4 3 2 1 2 3 4 5
  75%  4 3 2 1 2 3 4 5
  100% 4 3 2 1 2 3 4 5

    下一篇是apply族的其他函数......
【上一篇:82.关于使用for循环时的四种问题及解决方案】
【下一篇:84.关于apply族的lapply/sapply/vapply】

相关文章

网友评论

      本文标题:83.关于purrr包中的函数与apply函数的运行原理说明-十

      本文链接:https://www.haomeiwen.com/subject/qtmbaltx.html