# 13 Basic: apply family

df <-
data.frame(
x = sample(1:5, 10, replace = TRUE),
y = sample(1:5, 10, replace = TRUE, prob = c(0.5/5, 1/5, 2/5, 1/5, 0.5/5)),
z = sample(1:5, 10, replace = TRUE, prob = c(1.5/5, 0.9/5, 0.2/5, 0.9/5, 1.5/5))
)
df
#>    x y z
#> 1  1 3 5
#> 2  2 4 5
#> 3  3 3 2
#> 4  4 3 5
#> 5  4 2 4
#> 6  1 1 1
#> 7  1 4 2
#> 8  4 4 3
#> 9  1 2 5
#> 10 1 3 2
n_var <- ncol(df)
mean_var <- vector(mode = "numeric", length = n_var)
for (i in seq_along(mean_var)) {
mean_var[i] <- mean(df[[i]])
}
mean_var
#> [1] 2.2 2.9 3.4
colMeans(df)
#>   x   y   z
#> 2.2 2.9 3.4

## 13.1 Use function as argument

apply()函数族的核心思想是把 function 作为 argument，这是 R 这种函数式编程（functional programming）语言的一个重要特点。要理解这个做法，先来看一个简单的例子（来自“R for Data Science”一书的 For loops vs. functionals 小节）。

col_mean <- function(df) {
output <- vector("double", length(df))
for (i in seq_along(df)) {
output[i] <- mean(df[[i]])
}
return(output)
}

col_median <- function(df) {
output <- vector("double", length(df))
for (i in seq_along(df)) {
output[i] <- median(df[[i]])
}
return(output)
}
col_sd <- function(df) {
output <- vector("double", length(df))
for (i in seq_along(df)) {
output[i] <- sd(df[[i]])
}
return(output)
}

col_summary <- function(df, fun) {
out <- vector("double", length(df))
for (i in seq_along(df)) {
out[i] <- fun(df[[i]])
}
return(out)
}
col_summary(df, mean)
#> [1] 2.2 2.9 3.4
col_summary(df, median)
#> [1] 1.5 3.0 3.5
col_summary(df, sd)
#> [1] 1.3984118 0.9944289 1.5776213

col_summary <- function(df, fun) {
print(fun)
out <- vector("double", length(df))
for (i in seq_along(df)) {
out[i] <- fun(df[[i]])
}
return(out)
}
results <- col_summary(df, mean)  # effectively create a function object "fun" whose value is the function body of mean()
#> function (x, ...)
#> UseMethod("mean")
#> <bytecode: 0x000001dbc84a4e60>
#> <environment: namespace:base>

## 13.2 The basic usage of apply()

apply()函数族就是将需要重复多次的操作所对应的 function （可以是 anonymous function）作为 argument，来实现和for结构一样的效果。例如，想要实现与col_summary()相同的效果，使用apply()可以大幅缩短代码行数：

apply(X, MARGIN, FUN, ..., simplify = TRUE)

• X: an array, including a matrix. If X is not an array but an object of a class with a non-null dim value (such as a data frame), apply attempts to coerce it to an array via as.matrix if it is two-dimensional (e.g., a data frame) or via as.array.
• MARGIN: a vector giving the subscripts which the function will be applied over. E.g., for a matrix 1 indicates rows, 2 indicates columns.
• simplify: a logical indicating whether results should be simplified if possible.
apply(df, MARGIN = 2, FUN = mean)
#>   x   y   z
#> 2.2 2.9 3.4
apply(df, MARGIN = 2, FUN = median)
#>   x   y   z
#> 1.5 3.0 3.5
apply(df, MARGIN = 2, FUN = sd)
#>         x         y         z
#> 1.3984118 0.9944289 1.5776213

apply()本质上就是把FUN应用到df的指定维度上，实际操作的时候，apply()会先检测是要处理哪个 object，应用到哪个维度，然后把数据按照维度拆成一个个小的部分，然后把这些小的部分传递给FUN提供的函数作为输入参数来执行。

apply()的输出结果根据具体情况的不同，可以是 matrix、vector 或 list（simplify = FALSE）。

# matrix
apply(df, 2, \(x) x + 1)
#>       x y z
#>  [1,] 2 4 6
#>  [2,] 3 5 6
#>  [3,] 4 4 3
#>  [4,] 5 4 6
#>  [5,] 5 3 5
#>  [6,] 2 2 2
#>  [7,] 2 5 3
#>  [8,] 5 5 4
#>  [9,] 2 3 6
#> [10,] 2 4 3
# list
apply(df, 2, \(x) x + 1, simplify = FALSE)
#> $x #> [1] 2 3 4 5 5 2 2 5 2 2 #> #>$y
#>  [1] 4 5 4 4 3 2 5 5 3 4
#>
#> $z #> [1] 6 6 3 6 5 2 3 4 6 3 ## 13.3lapply() and sapply() lapply()sapply()可以视作是不同版本的apply() 1. lapply(X, FUN) • X: a vector (atomic or list). • FUN: the function to be applied to each element of X. lapply()输出的结果是一个 list。 lapply(df, mean) #>$x
#> [1] 2.2
#>
#> $y #> [1] 2.9 #> #>$z
#> [1] 3.4
lapply(c(1, 2, 3), \(x) x + 1)
#> [[1]]
#> [1] 2
#>
#> [[2]]
#> [1] 3
#>
#> [[3]]
#> [1] 4
1. sapply(X, FUN)

sapply()输出的结果视情况而定，可以是一个 vector、matrix 或 list。

# vector
sapply(df, mean)
#>   x   y   z
#> 2.2 2.9 3.4
# matrix
df_list <- lapply(1:3, \(x) data.frame(
x = sample(1:5, 10, replace = TRUE),
y = sample(1:5, 10, replace = TRUE, prob = c(0.5/5, 1/5, 2/5, 1/5, 0.5/5)),
z = sample(1:5, 10, replace = TRUE, prob = c(1.5/5, 0.9/5, 0.2/5, 0.9/5, 1.5/5))
))
sapply(df_list, colMeans)
#>   [,1] [,2] [,3]
#> x  2.3  3.1  2.9
#> y  2.7  2.9  2.9
#> z  2.4  2.0  2.4
# list
sapply(3:9, seq)
#> [[1]]
#> [1] 1 2 3
#>
#> [[2]]
#> [1] 1 2 3 4
#>
#> [[3]]
#> [1] 1 2 3 4 5
#>
#> [[4]]
#> [1] 1 2 3 4 5 6
#>
#> [[5]]
#> [1] 1 2 3 4 5 6 7
#>
#> [[6]]
#> [1] 1 2 3 4 5 6 7 8
#>
#> [[7]]
#> [1] 1 2 3 4 5 6 7 8 9

## 13.4 The basic usage of lapply() and sapply()

1. 循环element
set.seed(123)
scores <- list(
class_1 = sample(100, 32, replace = TRUE),
class_2 = sample(100, 36, replace = TRUE),
class_3 = sample(100, 30, replace = TRUE)
)
lapply(scores, mean)
#> $class_1 #> [1] 53.34375 #> #>$class_2
#> [1] 52.94444
#>
#> \$class_3
#> [1] 50.4
1. 循环subscript
set.seed(123)
scores <- list(
class_1 = sample(100, 32, replace = TRUE),
class_2 = sample(100, 36, replace = TRUE),
class_3 = sample(100, 30, replace = TRUE)
)
lapply(names(scores), \(x) mean(scores[[x]]))
#> [[1]]
#> [1] 53.34375
#>
#> [[2]]
#> [1] 52.94444
#>
#> [[3]]
#> [1] 50.4
set.seed(123)
scores <- list(
class_1 = sample(100, 32, replace = TRUE),
class_2 = sample(100, 36, replace = TRUE),
class_3 = sample(100, 30, replace = TRUE)
)
lapply(seq_along(scores), \(x) mean(scores[[x]]))
#> [[1]]
#> [1] 53.34375
#>
#> [[2]]
#> [1] 52.94444
#>
#> [[3]]
#> [1] 50.4

apply()函数族中使用匿名函数的小技巧

lapply()sapply()的第二种基本用法的例子中，scores没虽然没有作为参数和x一并传入给 anonymous function，但运行并不会受影响。结合 define function 章节（详见12）的知识可以知道，这是因为在定义的 anonymous function 运行时自动创建的独立 environment 中没有scores， R 自动到上一层 environment 里面找，就自然找到了在lapply()外定义的scores。这也就是为什么apply()函数族中使用 anonymous function 时，虽然只能接受一个由apply()函数族传递进来的输入参数x，但也可以使用在 anonymous function 之前就已经存在的 object，从而完成较为复杂的任务。

## 13.5 Recap

1. apply()函数族的核心思想是把 function 作为 argument；
2. 比较简单的重复任务可以考虑使用apply()函数族代替for结构；
3. 需要重复多次，但又不需要保存下供后续使用的操作，请使用 anonymous function。