R:避免在使用应用函数时将单行数据帧转换为向量

R: avoid turning one-row data frames into a vector when using apply functions

我经常遇到 R 将我的一列数据帧转换为字符向量的问题,我使用 drop=FALSE 选项解决了这个问题。

但是,在某些情况下,我不知道如何在 R 中解决此类行为,这就是其中之一。

我有一个如下所示的数据框:

mydf <- data.frame(ID=LETTERS[1:3], value1=paste(LETTERS[1:3], 1:3), value2=paste(rev(LETTERS)[1:3], 1:3))

看起来像:

> mydf
  ID value1 value2
1  A    A 1    Z 1
2  B    B 2    Y 2
3  C    C 3    X 3

我在这里做的任务是在除第一列之外的每一列中用 _ 替换空格,我想为此使用 apply 族函数,sapply在这种情况下。

我执行以下操作:

new_df <- as.data.frame(sapply(mydf[,-1,drop=F], function(x) gsub("\s+","_",x)))
new_df <- cbind(mydf[,1,drop=F], new_df)

生成的数据框看起来正是我想要的:

> new_df
  ID value1 value2
1  A    A_1    Z_1
2  B    B_2    Y_2
3  C    C_3    X_3

我的问题始于一些罕见的情况,在这些情况下,我的输入只能包含一行数据。出于某种原因,我一直不明白,R 在这些情况下有完全不同的行为,但没有 drop=FALSE 选项可以拯救我...

我现在的输入数据框是:

mydf <- data.frame(ID=LETTERS[1], value1=paste(LETTERS[1], 1), value2=paste(rev(LETTERS)[1], 1))

看起来像:

> mydf
  ID value1 value2
1  A    A 1    Z 1

但是,当我应用相同的代码时,生成的数据框看起来像这样丑陋:

> new_df
       ID sapply(mydf[, -1, drop = F], function(x) gsub("\\s+", "_", x))
value1  A                                                              A_1
value2  A                                                              Z_1

如何解决这个问题,使同一行代码为任意行数的输入数据帧提供相同类型的结果?

一个更深层次的问题是 R 到底为什么要这样做?当我有一些新的奇怪输入时,我会继续回到我的代码 row/column 因为它们破坏了一切......谢谢!

您可以使用 lapply 而不是 sapply 来解决您的问题,然后使用 do.call 组合结果如下

new_df <- as.data.frame(lapply(mydf[,-1,drop=F], function(x) gsub("\s+","_",x)))
new_df <- do.call(cbind, new_df)
new_df
#     value1 value2
#[1,] "A_1"  "Z_1" 

new_df <- cbind(mydf[,1,drop=F], new_df)
#new_df
#  ID value1 value2
#1  A    A_1    Z_1

至于你关于sapply的不可预测行为的问题,是因为sapply中的s代表简化,但简化后的结果不保证是数据框。它可以是数据框、矩阵或向量。

根据sapply的文档:

sapply is a user-friendly version and wrapper of lapply by default returning a vector, matrix or, if simplify = "array", an array if appropriate, by applying simplify2array().

关于 simplify 参数:

logical or character string; should the result be simplified to a vector, matrix or higher dimensional array if possible? For sapply it must be named and not abbreviated. The default value, TRUE, returns a vector or matrix if appropriate, whereas if simplify = "array" the result may be an array of “rank” (=length(dim(.))) one higher than the result of FUN(X[[i]]).

详细信息部分解释了它的行为与您所经历的不相似(重点来自我):

Simplification in sapply is only attempted if X has length greater than zero and if the return values from all elements of X are all of the same (positive) length. If the common length is one the result is a vector, and if greater than one is a matrix with a column corresponding to each element of X.

Hadley Wickham 也建议不要使用 sapply:

I recommend that you avoid sapply() because it tries to simplify the result, so it can return a list, a vector, or a matrix. This makes it difficult to program with, and it should be avoided in non-interactive settings

他还建议不要将 apply 与数据框一起使用。请参阅 Advanced R 了解更多说明。

您还可以使用 purrr 包中的 map_df 函数,它对对象的每个元素以及 returns 数据框应用一个函数:

library(dplyr)
library(purrr)

mydf %>%
  mutate(map_df(select(cur_data(), starts_with("value")), ~ gsub("\s", "_", .x)))

  ID value1 value2
1  A    A_1    Z_1

与原始数据框:

  ID value1 value2
1  A    A_1    Z_1
2  B    B_2    Y_2
3  C    C_3    X_3

这是一个替换原始数据的解决方案。不过,不确定这是否适用于您的工作流程。请注意,我使用了 apply 用于按行或列处理 data.frames。

mydf <- data.frame(ID=LETTERS[1], value1=paste(LETTERS[1], 1), value2=paste(rev(LETTERS)[1], 1))

xy <- apply(X = mydf[, -1, drop = FALSE],
      MARGIN = 2,
      FUN = function(x) gsub("\s+", "_", x),
      simplify = FALSE
)
xy <- do.call(cbind, xy)
xy <- as.data.frame(xy)

mydf[, -1] <- as.data.frame(xy)
mydf

  ID value1 value2
1  A    A_1    Z_1