当使用dplyr的mutate_at函数时,如何处理需要多个参数且这些参数对应不同列的函数?

5

我有一个data.frame,其中有许多列的名称遵循一定的模式。例如:

df <- data.frame(
  x_1 = c(1, NA, 3), 
  x_2 = c(1, 2, 4), 
  y_1 = c(NA, 2, 1), 
  y_2 = c(5, 6, 7)
)

我希望使用mutate_at在每一对列上执行相同的操作,就像这样:
df %>%
  mutate(
    x = ifelse(is.na(x_1), x_2, x_1), 
    y = ifelse(is.na(y_1), y_2, y_1)
  )

请问我能用mutate_at/mutate_each来实现这个吗?

以下是内容:

df %>%
  mutate_each(vars(x_1, y_1), funs(ifelse(is.na(.), vars(x_2, y_2), .)))

我尝试了各种不同的变化,但都失败了。

这个问题类似于在dplyr mutate_at调用中使用多列函数,但不同之处在于函数调用的第二个参数不是单独的一列,而是vars中每一列对应的不同列。

提前感谢您的帮助。


我现在正在处理一个类似的东西。这是与我的先前问题相同的问题:https://stackoverflow.com/questions/47005763/looping-across-multiple-variables-and-parameters-using-map-and-mutate,但在这种情况下,数据集太大了,导致RStudio崩溃。 - LightonGlass
一个 data.tableset 循环可能是做这件事情最快的方法之一。dplyr::coalesce 也许更易读一些。 - zacdav
3个回答

3

我不知道你是否能够通过这种方式来解决问题,但是这里有一个不同的角度。如果你发现自己拥有非常宽的数据(例如,大量带有相似名称的列)并且想要对其进行操作,那么使用tidyr::gather(请参见此处的文档http://tidyr.tidyverse.org/)可以帮助将数据“整理”(在stata术语中为long)。

> df %>% gather()
   key value
1  x_1     1
2  x_1    NA
3  x_1     3
4  x_2     1
5  x_2     2
6  x_2     4
7  y_1    NA
8  y_1     2
9  y_1     1
10 y_2     5
11 y_2     6
12 y_2     7

将数据转换为这种格式后,使用group_by更容易组合和重新排列值,而不是尝试使用mutate_at。例如,您可以使用df %>% gather() %>% mutate(var = substr(key,1,1))获取第一个值,并使用group_by(var)以不同的方式操作xy


我认为这会产生相反的效果。在进行“gather”操作之后,我将面临按键前缀和原始行的唯一标识符进行分组的问题。 - Bob
为了详细说明问题,我的 data.frame 包含了117个不同项目的数据,这些数据是样本的实验室测试结果,其中样本是由不同的实验室分批测试的。因此,每个项目有7列数据--样本测量值、取样值的范围以及关于批次的各种数据,包括批次方差和校准信息。因此,需要进行相当大量的处理才能使这些值归一化并保持一致。 - Bob
总共是7列还是117列?你的列越多(并且列操作越复杂),就越难让mutate函数族做你想要的事情。你可能想要使它更整洁(参见,例如,https://cran.r-project.org/web/packages/tidyr/vignettes/tidy-data.html),并使用`group_by`,它被设计用来处理这种类型的问题。或者你可以切换到基本的R操作(这可能更容易处理复杂的列操作)。 - twedl
它有819列。每个117个测量变量有7列。将原始测量转换为可用测量的过程,使用每个测量变量的其他6列,对于这117个变量来说是相同的。这就是为什么我正在寻找一种基于mutate_函数族的方法。到目前为止,我能想到的方法是创建7个矩阵,每个矩阵有117列,但这是一种非常不幸的方法,会使代码变得非常复杂。 - Bob

1

虽然这个问题比较老,但我同意Jesse的观点,你需要整理一下数据。使用gather是可行的方法,但它缺少像stats::reshape那样可以指定要整理的列组的功能。因此,这里提供了一个使用reshape的解决方案:

df %>% 
   reshape(varying   = list(c("x_1", "y_1"), c("x_2", "y_2")), 
           times     = c("x", "y"),
           direction = "long") %>% 
   mutate(x = ifelse(is.na(x_1), x_2, x_1)) %>% 
   reshape(idvar     = "id", 
           timevar   = "time",
           direction = "wide") %>% 
   rename_all(funs(gsub("[a-zA-Z]+(_*)([0-9]*)\\.([a-zA-Z]+)", "\\3\\1\\2", .)))
#   id x_1 x_2 x y_1 y_2 y
# 1  1   1   1 1  NA   5 5
# 2  2  NA   2 2   2   6 2
# 3  3   3   4 3   1   7 1

为了实现任意列对的操作,您可以这样做:
df2 <- setNames(cbind(df, df), c(t(outer(letters[23:26], 1:2, paste, sep = "_"))))
v <- split(names(df2), purrr::map_chr(names(df2), ~ gsub(".*_(.*)", "\\1", .)))
n <- unique(purrr::map_chr(names(df2), ~ gsub("_[0-9]+", "", .) ))
df2 %>% 
    reshape(varying   = v, 
            times     = n,
            direction = "long") %>% 
     mutate(x = ifelse(is.na(!!sym(v[[1]][1])), !!sym(v[[2]][1]), !!sym(v[[1]][1]))) %>% 
     reshape(idvar     = "id", 
             timevar   = "time",
             direction = "wide") %>% 
     rename_all(funs(gsub("[a-zA-Z]+(_*)([0-9]*)\\.([a-zA-Z]+)", "\\3\\1\\2", .)))
#   id w_1 w_2 w x_1 x_2 x y_1 y_2 y z_1 z_2 z
# 1  1   1   1 1  NA   5 5   1   1 1  NA   5 5
# 2  2  NA   2 2   2   6 2  NA   2 2   2   6 2
# 3  3   3   4 3   1   7 1   3   4 3   1   7 1

这假定应进行比较的列相邻,并且所有可能包含NA值的列都在以_1为后缀的列中,替换值列以_2为后缀。

我认为这是正确的,谢谢。我同意数据需要整理--这个操作是一系列旨在整理数据的步骤中的早期步骤。 - Bob

-3

当我问这个问题时,答案是“你不能!”但现在不再是这样了,因为tidyr现在支持pivot_widerpivot_longer


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接