R语言中 "apply" 函数详解

2021-04-21 10:55:10 浏览数 (4)

磐创AI分享

作者 | PURVA HUILGOL

编译 | Flin

来源 | analyticsvidhya

概述

  • 数据操作是机器学习生命周期中最关键的步骤之一
  • 让我们学习最广泛使用的apply函数集来转换R中的数据

介绍

数据操作是机器学习生命周期中最关键的步骤之一。它需要转换所提供的数据,以便用于建立预测模型。

此外,一个熟练的数据科学家运用他们的直觉和经验,从数据中提取尽可能多的信息。因此,在Python和R中都有大量的函数和工具可以帮助我们完成这项任务,这一点也不奇怪。

今天,我们将使用R并学习在R中转换数据时使用最广泛的一组“apply”函数。这组函数提供了对数据的高效和快速操作。当我们只想处理某些列时,这特别有用。这组函数称为apply()函数。除了sapply()、mapply()等变体之外,我们还提供了一把用于数据处理的多用途"瑞士军刀"。

如果你对从事数据科学和学习这些东西感兴趣,我建议你看看我们的Certified AI & ML BlackBelt Accelerate计划。

  • https://blackbelt.analyticsvidhya.com/accelerate

目录

该系列中的各种函数包括:

  • 设置上下文
  • apply
  • lapply
  • sapply
  • vapply
  • tapply
  • mapply
设置上下文

我将首先通过使用简单的数据集介绍上面的每个函数是如何工作的,然后我们将使用一个真实的数据集来使用这些函数。

让我们开始吧!

我们不需要安装任何其他库来使用apply函数。因此,让我们首先创建一个简单的数值矩阵,从1到20,分布在5行4列中:

代码语言:javascript复制
data <- matrix(c(1:20), nrow = 5 , ncol = 4)
data

这就是我们矩阵的样子。现在,让我们从apply()函数开始

apply()

apply() 函数的一般语法可以通过帮助部分获得。只需执行此代码即可获得详细的文档

代码语言:javascript复制
?apply

我们可以看到,apply函数的结构是apply(X,MARGIN,FUN,…)。

这里,

  • X是指我们将对其应用操作的数据集(在本例中是矩阵)
  • MARGIN参数允许我们指定是按行还是按列应用操作
  • 行边距=1
  • 列边距=2
  • FUN指的是我们想要在X上“应用”的任何用户定义或内置函数

让我们看看计算每行平均数的简单示例:

代码语言:javascript复制
mean_rows <- apply(data, 1, mean)
mean_rows

那相当简单!我们可以看到如何使用apply()函数来总结数据。同样,让我们试着沿着每列求和:

代码语言:javascript复制
sum_cols <- apply(data, 2, sum)
sum_cols

如果我们想在所有元素上应用函数,我们只需这样编写apply函数:

代码语言:javascript复制
all_sqrt <- apply(data, 1:2, sqrt)
all_sqrt

如果我们想对数据应用一个用户定义的函数呢?例如,我有一个函数,它为每行查找(x–1)的平方根:

代码语言:javascript复制
fn = function(x)
{
   return(sqrt(x - 1))
}

然后,我们在每行应用此函数:

代码语言:javascript复制
apply(data, 1, fn)

到目前为止,我们只使用了一个参数的函数,并将它们应用于数据。apply家族最棒的部分是,它们也处理具有多个参数的函数!让我们应用一个用户定义的函数,该函数包含3个参数:

代码语言:javascript复制
fn = function(x1, x2, x3)
{    
    return(x1^2   x2 * x1   x3)
}

我们将x1作为'data'中的每个值,将x2,x3作为其他参数,这些参数将首先声明,然后通过apply函数传递:

代码语言:javascript复制
b = 2
c = 1

# apply along each row:
row_fn <- apply(data, 1, fn, x2 = b, x3 = c)

# apply along each column:
col_fn <- apply(data, 2, fn, x2 = b, x3 = c)

让我们检查一下第fn行和第fn列

代码语言:javascript复制
row_fn
代码语言:javascript复制
col_fn

apply()系列的其余部分遵循类似的结构,除了一些更改外,其他参数也类似。接下来让我们使用lappy()函数。

lapply()

上面的apply()函数有一个约束,数据必须是至少2维的矩阵,apply()函数才能对其执行。lapply()函数删除了这个约束。lapply()是list apply的缩写,可以对列表或向量使用lapply函数。无论是一个向量列表还是一个简单的向量,lappy()都可以在这两个向量上使用。由于我们现在处理的是向量/列表,lapply函数也不需要MARGIN参数。也就是说,lapply的返回类型也是一个列表。

它仅将数据和函数作为基本参数:

lapply(X, FUN)

让我们看一些例子:

代码语言:javascript复制
# define a list
cart <- c("BREAD","BUTTER","MILK","COOKIES")

# use lapply to convert all to lower case
cart_lower <- lapply(cart, tolower)

#output
cart_lower

我们现在要看一个更复杂的列表:

代码语言:javascript复制
data <- list(l1 = c(1, 2, 3, 4),
             l2 = c(5, 6, 7, 8),
             l3 = c(9, 10, 11, 12))

# apply the 'sum' function on data:
sum_list <- lapply(data, sum)

#output
sum_list
sapply()

sapply()函数(simplified apply的缩写)类似于lappy函数。唯一的区别是输出的返回类型——sapply()根据返回的值简化了输出。我创建了一个简单的表,告诉我们返回的类型:

返回值

每个元素的长度

输出

列表

1个

向量

列表

> 1并且长度相同

矩阵

列表

> 1,且长度可变

列表

我们将看到上述所有场景的示例:

场景1:每个元素的长度=1

代码语言:javascript复制
data <- list(l1 = c(1, 2, 3, 4))

# apply the 'sum' function on data:
sum_sapply1 <- sapply(data, sum)

#output
sum_sapply1

使用lapply查看输出的差异:

代码语言:javascript复制
sum_lapply1 <- lapply(data, sum)
sum_lapply1

场景2:每个元素的长度>1且相同

代码语言:javascript复制
data <- list(l1 = c(1, 2, 3, 4),
             l2 = c(5, 6, 7, 8),
             l3 = c(9, 10, 11, 12))

# apply the 'sum' function on data:
sum_sapply2 <- sapply(data, sum)

#output
sum_sapply2

lapply()提供了什么输出?

代码语言:javascript复制
sum_lapply2 <- lapply(data, sum)
sum_lapply2

场景3:每个元素的长度>1且不同

代码语言:javascript复制
data <- list(l1 = c(1, 2, 3),
l2 = c(5, 6, 7, 8),
l3 = c(9, 10))

# apply the 'sum' function on data:
sum_sapply3 <- sapply(data, sum)

#output
sum_sapply3

让我们将其与lappy()在相同数据上的输出进行比较:

代码语言:javascript复制
sum_lapply3 <- lapply(data, sum)

#output
sum_lapply3

你可以看到输出与上面返回列表的lappy有何不同

vapply()

来到vapply()函数。lapply()、apply()和vapply()这三个函数是专门为所有类型的向量定制的。与lappy()和sapply()为我们决定输出的数据类型不同,vapply()允许我们选择输出结构的数据类型。因此,vapply()的参数是:

vapply(X,FUN,FUN.VALUE)

在这里 FUN.VALUE 用于提供所需的数据类型。

当lost/vectors包含数字和字符串的混合时,这是最有用的:

代码语言:javascript复制
data <- list(l1 = c(1, 2, 3, 4),
             l2 = c(5, 6, 7, 8),
             l3 = c(9, 10, 11, 12),
             l4 = c("a", "b", "c", "a"))

# apply the 'max' function on data:
sum_vapply <- vapply(data, max, numeric(1))

正如预期的那样,我们得到了一个错误,因为无法从字符列表中计算最大值。numeric(1)指定我们希望输出为单个数值,其中每个元素的长度为1。如果我们使用lapply()或sapply()呢?

代码语言:javascript复制
lapply(data, max)
sapply(data, max)

因此,我们可以看到lappy()和sapply()实际上都提供了相同的输出。实际上,sapply()甚至将输出转换为character类型的向量。理想情况下,这不是我们想要的。

通常,这就是我们使用vapply()函数的方式

代码语言:javascript复制
data <- list(l1 = c(1, 2, 3, 4),
             l2 = c(5, 6, 7, 8),
             l3 = c(9, 10, 11, 12))

max_vapply <- vapply(data, max, numeric(1))

max_vapply

因此,在处理具有不同数据类型特性的数据帧时,最好使用vapply()。

tapply()

简单地说,tapply()允许我们将数据分组,并对每个分组执行操作。因此,当你提供一个向量作为输入时,tapply()会对向量的每个子集执行指定的操作。需要的参数包括:

tapply(X, INDEX, FUN)

其中INDEX表示要用于分隔数据的因子。听起来耳熟吗?是的,tapply()只不过是执行groupy操作并对分组数据应用某些函数的简单方法!

为了观察tapply()的工作原理,让我们创建两个简单的向量

代码语言:javascript复制
item_cat <- c("HOME", "SNACKS", "BEVERAGE", "STORAGE", "CLEANING", "STORAGE", "HOME", "BEVERAGE", "ELECTRONICS", "SNACKS")
item_qty <-c(25, 30, 45, 66, 15, 50, 35, 20, 15, 35)

现在让我们使用tapply来获得每个项目类别的平均数量:

代码语言:javascript复制
tapply(item_qty, item_cat, mean)

tapply()函数做了什么?我们将item_qty向量按item_cat向量分组,以创建向量的子集。然后我们计算每个子集的平均值。

使用tapply()非常容易,因为它会自动从item_cat 向量 中获取唯一的值,并几乎立即对数据应用所需的函数。我们甚至可以在每个子集上获得多个值:

代码语言:javascript复制
tapply(item_qty, item_cat, function(x) c(mean(x), sum(x)))

现在,我们来看看apply()函数家族中的最后一个函数——mapply()函数。

mapply()

mapply()代表multivariable apply,基本上是sapply()的multivariable版本。mapply函数最好用例子来解释——所以让我们先使用它,然后再尝试理解它是如何工作的。

首先,让我们看一个通常不以2个列表或2个向量作为参数的函数,例如max函数。我们先看两张列表:

代码语言:javascript复制
list1 <- list(a = c(1, 2, 3), b = c(4, 5, 6), c = c(7, 8, 9))
list2 <- list(a = c(10, 11, 12), b = c(13, 14, 15), c = c(16, 17, 18))

现在,如果我们想找出每对列表元素之间的最大值呢?

代码语言:javascript复制
max(list1$a, list2$a)

现在,这个函数不能同时应用于list1和list2的所有元素。在这种情况下,我们使用mapply()函数:

代码语言:javascript复制
mapply(function(num1, num2) max(c(num1, num2)), list1, list2)

因此,mapply函数用于对通常不接受多个列表/向量作为参数的数据执行函数。当你要创建新列时,它也很有用。让我们首先从最初定义的矩阵创建一个数据帧:

代码语言:javascript复制
df <- as.data.frame(data)

现在,我们将创建一个新变量,该变量包含V1列和V3列的乘积:

代码语言:javascript复制
mapply(function(x, y) x/y, df$V1, df$V3)

因此,在处理数据帧时,mapply是一个非常方便的函数。

现在,让我们看看如何在实际数据集上使用这些函数。为了简单起见,让我们使用iris数据集:

代码语言:javascript复制
iris_df<-datasets::iris

head(iris_df)

我们现在可以使用apply()函数计算每行的间隔长度和间隔宽度的平均值:

代码语言:javascript复制
iris_df['Sepal_mean'] <- apply(iris_df[c("Sepal.Length", "Sepal.Width")], 1, mean)

类似地,我们可以获得数据框中每个物种的每列的摘要值:

代码语言:javascript复制
tapply(iris_df$Sepal.Width, iris_df$Species, mean)

我们还可以使用mapply()函数创建一个显示花瓣长度和花瓣宽度之和的新列:

代码语言:javascript复制
iris_df['Sum_Petal'] <- mapply(function(x, y) x y, iris_df$Petal.Length, iris_df$Petal.Width)

尾注

到目前为止,我们学习了R中apply()函数族中的各种函数。这些函数集提供了在一瞬间对数据应用各种操作的极其有效的方法。本文介绍了这些函数的基础知识,目的是让你了解这些函数是如何工作的。

我鼓励你在更复杂的数据集上尝试更复杂的函数,以充分了解这些函数有多有用。

1 人点赞