嵌套时聚合函数的范围 apply(within())

Posted

技术标签:

【中文标题】嵌套时聚合函数的范围 apply(within())【英文标题】:Scope of Aggregation Functions when nesting apply(within()) 【发布时间】:2020-06-26 19:53:49 【问题描述】:

编辑原始帖子以澄清问题

背景 我正在学习 R 并看到了这种情况,但不明白 R 如何处理(我将称之为)隐含的上下文转换。我试图理解的脚本只是遍历矩阵的每一行并打印该行中包含该行最小值的列的索引。我不明白的是R如何处理上下文转换,因为不同的函数被应用于因变量x

    x(当定义为function(x) 的参数时)是一个原子向量,因为apply() 函数带有MARGIN = 1 参数 which() 函数然后遍历原子向量 x 中的各个 元素 以查看哪些 == min(x) 这是真正让我感到困惑的部分: 尽管which() 正在迭代原子向量x元素,但您可以在@987654332 中调用min(x) @ 函数和 R 以某种方式切换 x 以再次定义为整个原子向量,以计算整个向量与单个元素范围内的min()

示例数据矩阵

a <- matrix (c(5, 2, 7, 1, 2, 8, 4, 5, 6), 3, 3)
         [,1] [,2] [,3]
[1,]    5    1    4
[2,]    2    2    5
[3,]    7    8    6

这是返回我难以理解的列索引的脚本

apply (a, 1, function(x) which(x == min(x)))

我的问题:

which() 函数中,为什么min(x) 会返回原子向量 的最小值(根据需要),而不是单个元素范围内的最小值 em> 在该向量中,因为 which() 正在遍历 原子向量 x?

中的每个单独元素

【问题讨论】:

x 是匿名函数,lambda。 min(x) 返回每​​行的最小值,因为这里的 MARGIN 是 1 i.le。对于行 apply 将整行传递给函数,一次一个。这是因为MARGIN = 1(第二个参数)。我相信这对 1. 和 2. 都有答案。 旁注:构造which(x == min(x)) 用于有多个最小值时;要使索引达到 first 最小使用 which.min(x). function(x) 是一个匿名函数x 是该函数中的参数。 【参考方案1】:

编辑:关于whichx的讨论:

您问题上的first comment 是不正确

x 是匿名函数,lambda

x 只是一个变量,没什么特别的。 function(x) 将其声明为匿名函数的第一个(也是唯一一个)参数,之后对 x 的每个引用都将引用传递给此匿名函数的内容;

代码使用了一个匿名函数;通常,您在 R 中所做的几乎所有事情都使用命名函数(例如,meanmin)。在某些情况下(例如,在apply 和相关函数中),将整个函数定义为参数而不是命名它是有意义的,如

## anonymous (unnamed) function
apply(m, 1, function(x) which(x == min(x)))

## equivalently, with a named function
myfunc <- function(x) which(x == min(x))
apply(m, 1, myfunc)

在第一种情况下,function(x) which(x == min(x))) 没有命名,所以它是“匿名的”。两次apply 调用之间的结果是相同的。

鉴于该上下文,x 是函数的第一个参数(myfunc 或您的情况下的匿名函数)。与下面的apply/MARGIN 讨论的其余部分,

x(在这种情况下)包含整行(当MARGIN=1);

min(x) 返回x 内的最小值的,长度始终为1);和

which(x == min(x)) 返回x 中最低值的索引;在这种情况下,它总是长度为 1 或更大,因为我们确信总有一个元素等于该向量的最小值……但是,不能保证 which 会找到任何匹配,因此which(...) 的返回值的长度可以介于 0 和输入的长度之间。例子:

which(11:15 == 13)
# [1] 3
which(11:15 == 1:5)
# integer(0)
which(11:15 == 11:15)
# [1] 1 2 3 4 5
which(11:15 %in% c(12, 14))
# [1] 2 4

apply 一次处理一个或多个维度。现在,我将坚持使用 2d 矩阵,在这种情况下,MARGIN= 选择行或列。 (有一个警告,见下文。)

我将使用逐步详细的函数来尝试显示每个步骤。我将其命名为anonfunc,但在您的脑海中,稍后将apply(a, 1, anonfunc) 转换为apply(a, 1, function(x) ... ),您将看到我打算做什么。另外,我有一个dematrix 函数来帮助显示anonfunc 中使用的内容。

dematrix <- function(m, label = "") 
  if (!is.matrix(m)) m <- matrix(m, nrow = 1)
  out <- capture.output(print(m))[-1]
  out <- gsub("^[][,0-9]+", "", out)
  paste(paste0(c(label, rep(strrep(" ", nchar(label)), length(out) - 1)), out),
        collapse = "\n")

anonfunc <- function(x) 
  message(dematrix(x, "Input: "))
  step1 <- x == min(x)
  message(dematrix(step1, "Step1: "))
  step2 <- which(step1)
  message("Step2: ", paste(step2, collapse = ","), "\n#\n")
  step2

二维数组

我将通过添加一列来稍微修改您的示例数据。这有助于可视化有多少函数调用以及函数的输入有多大。

apply(a, 1, anonfunc)
# Input:     5    1    4   11
# Step1:  FALSE TRUE FALSE FALSE
# Step2: 2
# #
# Input:     2    2    5   12
# Step1:  TRUE TRUE FALSE FALSE
# Step2: 1,2
# #
# Input:     7    8    6   13
# Step1:  FALSE FALSE TRUE FALSE
# Step2: 3
# #
# [[1]]
# [1] 2
# [[2]]
# [1] 1 2
# [[3]]
# [1] 3

我们的匿名函数被调用了 3 次,每行调用一次。在每次调用中,都会传递一个长度为 4 的向量,即矩阵中一行的大小。

请注意,我们会得到一个list 作为回报。通常apply 返回一个向量或矩阵。返回值实际上是MARGIN= 轴的维度,加上返回值长度的维度。也就是说,a 的尺寸为 3x4;如果每次调用 anon-func 的返回值是长度 1,那么返回值是“某种”3x1,但 R 将其简化为长度为 3 的向量(这可能在数学上被解释为不一致,我不不同意)。;如果每个 anon-func 调用的返回值的长度为 10,那么输出将是一个 3x10 的矩阵。

但是,当任何 anon-func 返回的长度/大小/类与其他函数不同时,apply 将返回 list。 (这与sapply 的行为相同,如果它在您不期望的情况下发生变化,可能会令人沮丧。据称R-devel 中有一个补丁允许我们使用apply(..., simplify=FALSE) 强制列表。)

如果我们改为使用MARGIN=2,我们将在列上进行操作:

apply(a, 2, anonfunc)
# Input:     5    2    7
# Step1:  FALSE TRUE FALSE
# Step2: 2
# #
# Input:     1    2    8
# Step1:  TRUE FALSE FALSE
# Step2: 1
# #
# Input:     4    5    6
# Step1:  TRUE FALSE FALSE
# Step2: 1
# #
# Input:    11   12   13
# Step1:  TRUE FALSE FALSE
# Step2: 1
# #
# [1] 2 1 1 1

现在,每列调用一次(4 次调用),x 是长度为 3 的向量(源矩阵中的行数)。

一次可以在多个轴上操作;虽然使用matrix(二维数组)似乎没有意义,但使用更大维度的数组更有意义。

apply(a, 1:2, anonfunc)
# Input:     5
# Step1:  TRUE
# Step2: 1
# #
# Input:     2
# Step1:  TRUE
# Step2: 1
# #
# Input:     7
# Step1:  TRUE
# Step2: 1
# #
# ...truncated... total of 12 calls to `anonfunc`
# #
#      [,1] [,2] [,3] [,4]
# [1,]    1    1    1    1
# [2,]    1    1    1    1
# [3,]    1    1    1    1

根据对输出尺寸的讨论,MARGIN=1:2 表示输出尺寸将是边距的尺寸——3x4——与输出的尺寸/长度。由于这里的输出总是长度为 1,那么从技术上讲就是 3x4x1,在 R-speak 中是一个暗淡的 3x4 矩阵。

每个边距使用矩阵的图片:

3d 数组

让我们稍微放大一点,看看一些“平面”操作。

a3 <- array(1:24, dim = c(3,4,2))
a3
# , , 1
#      [,1] [,2] [,3] [,4]
# [1,]    1    4    7   10
# [2,]    2    5    8   11
# [3,]    3    6    9   12
# , , 2
#      [,1] [,2] [,3] [,4]
# [1,]   13   16   19   22
# [2,]   14   17   20   23
# [3,]   15   18   21   24

MARGIN=1 开头。当您看到两个数组时,请查看第一个 Input: 并查看原始 a3 数组中使用的是哪个“平面”。它看起来是转置的,当然......

为简洁起见(为时已晚!),我将缩写 anonfunc 的第三次和后续迭代以仅显示详细输出的第一行(内部矩阵行)。

apply(a3, 1, anonfunc)
# Input:     1   13
#            4   16
#            7   19
#           10   22
# Step1:   TRUE FALSE
#         FALSE FALSE
#         FALSE FALSE
#         FALSE FALSE
# Step2: 1
# #
# Input:     2   14
#            5   17
#            8   20
#           11   23
# Step1:   TRUE FALSE
#         FALSE FALSE
#         FALSE FALSE
#         FALSE FALSE
# Step2: 1
# #
# Input:     3   15 ...
# #
# [1] 1 1 1

同样,MARGIN=2。我将再次显示a3,以便您查看正在使用哪个“平面”:

a3
# , , 1
#      [,1] [,2] [,3] [,4]
# [1,]    1    4    7   10
# [2,]    2    5    8   11
# [3,]    3    6    9   12
# , , 2
#      [,1] [,2] [,3] [,4]
# [1,]   13   16   19   22
# [2,]   14   17   20   23
# [3,]   15   18   21   24

apply(a3, 2, anonfunc)
# Input:     1   13
#            2   14
#            3   15
# Step1:   TRUE FALSE
#         FALSE FALSE
#         FALSE FALSE
# Step2: 1
# #
# Input:     4   16
#            5   17
#            6   18
# Step1:   TRUE FALSE
#         FALSE FALSE
#         FALSE FALSE
# Step2: 1
# #
# Input:     7   19 ...
# Input:    10   22 ...
# #
# [1] 1 1 1 1

MARGIN=3 不是很令人兴奋:anonfunc 只调用了两次,每个面向前面的“平面”调用一次(此处无需缩写):

apply(a3, 3, anonfunc)
# Input:     1    4    7   10
#            2    5    8   11
#            3    6    9   12
# Step1:   TRUE FALSE FALSE FALSE
#         FALSE FALSE FALSE FALSE
#         FALSE FALSE FALSE FALSE
# Step2: 1
# #
# Input:    13   16   19   22
#           14   17   20   23
#           15   18   21   24
# Step1:   TRUE FALSE FALSE FALSE
#         FALSE FALSE FALSE FALSE
#         FALSE FALSE FALSE FALSE
# Step2: 1
# #
# [1] 1 1

一个可以在这里也使用多个维度,这就是我认为Input: 字符串变得有点清晰的地方:

a3
# , , 1
#      [,1] [,2] [,3] [,4]
# [1,]    1    4    7   10
# [2,]    2    5    8   11
# [3,]    3    6    9   12
# , , 2
#      [,1] [,2] [,3] [,4]
# [1,]   13   16   19   22
# [2,]   14   17   20   23
# [3,]   15   18   21   24

apply(a3, 2:3, anonfunc)
# Input:     1    2    3
# Step1:  TRUE FALSE FALSE
# Step2: 1
# #
# Input:     4    5    6
# Step1:  TRUE FALSE FALSE
# Step2: 1
# #
# Input:     7    8    9 ...
# Input:    10   11   12 ...
# Input:    13   14   15 ...
# Input:    16   17   18 ...
# Input:    19   20   21 ...
# Input:    22   23   24 ...
# #
#      [,1] [,2]
# [1,]    1    1
# [2,]    1    1
# [3,]    1    1
# [4,]    1    1

由于a3 的维度是 3,4,2,并且我们正在查看边距2:3,并且每次调用anonfunc 返回长度1,我们返回的矩阵是4x2x1(x1 被 R 静默删除)。

要查看MARGIN= 的每个调用实际使用的内容,请参见下图:

【讨论】:

非常感谢您对不同维度集的 apply() 范围的解释。在我完全理解之前,我会多次重读这篇文章:)。我最初的问题(即使陈述不佳)主要集中在 inside() 函数中发生的事情。你在(x==min(x)) 内调用。这里 x 迭代行的每个元素,而 min(x) 似乎“弹出”到整行的更大范围。我应该如何理解这种范围切换?有没有一个名字可以让我了解发生这种情况时的明确规则?【参考方案2】:

“词法作用域根据函数在创建时的嵌套方式查找符号值,而不是在调用时它们的嵌套方式。使用词法作用域,您不需要知道函数是如何被调用来确定变量的值将被查找到哪里。你只需要查看函数的定义。"**

**来源:http://adv-r.had.co.nz/Functions.html#lexical-scoping

【讨论】:

以上是关于嵌套时聚合函数的范围 apply(within())的主要内容,如果未能解决你的问题,请参考以下文章

装饰器与耦合聚合

为啥 CROSS APPLY 与列和聚合函数需要 Group by

apply和call的适用范围

H2 用户使用“WITHIN GROUP”定义的 ListAgg 函数

Pool.apply_async():嵌套函数未执行

如何在 R 或 RStudio 中的 apply() 函数中嵌套 quantile() 函数