磐创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()函数族中的各种函数。这些函数集提供了在一瞬间对数据应用各种操作的极其有效的方法。本文介绍了这些函数的基础知识,目的是让你了解这些函数是如何工作的。
我鼓励你在更复杂的数据集上尝试更复杂的函数,以充分了解这些函数有多有用。