dplyr对多列进行数据变换

3

我希望对数据框中一系列列进行操作。假设这个数据集如下:

set.seed(15)
df <- data.frame(id=letters[1:10], matrix(runif(5*10), nrow=5)) %>% dplyr::rename(O6 = X7)

我认为我的目标是相当明显的:

df %>% rowwise() %>% mutate(minval_X3_X8 = min(X3:X8))

获取ie结果的方法:

df %>% rowwise() %>% mutate(minval_X3_X8 = min(X3, X4, X5, O6, X7, X8))

我尝试使用X3:X8没有收到任何错误信息,因此我想知道:
1. 使用dplyr实现从列X3到X8获取最小/最大/平均值的最佳方法是什么?
2. 当使用X3:X8时,我实际上得到了什么?

非常感谢!

附注:如果解决方案还包括以下操作,那就太好了:

df %>% rowwise() %>% mutate(minval_all_but_ex_rownames = min(-id))

1
我认为列的范围(X3:X8)在 select 中有效,但在 mutate 或相关函数中调用时无效。(如果有人知道不同,请告诉我。) - r2evans
@r2evans 我找到了一份资源,其中指出您不能在 mutate 中使用 select。该页面上有一个非官方的“解决方案”。然而,当我尝试将其用于此上下文时,它未能正常工作:https://github.com/tidyverse/dplyr/issues/2050 - DTYK
你好r2evans。我一开始并没有意识到这是我的任务来指定我认为的“最佳”答案,我以为这是由社区根据赞数选择的。谢谢你的提示。非常感谢大家,我从每个提出的解决方案中都学到了东西 :) - Yvan
4个回答

4

dplyr::select 可以使用 X3:X7 的范围表示法,但不能使用其他函数。如果您可以在 mutate 之外工作,请尝试以下操作:

df$minval_X3_X8 <- do.call(pmin, select(df, X3:X8))
df
#    id        X1        X2        X3        X4         X5        O6        X7        X8        X9       X10 minval_X3_X8
# 1   a 0.6021140 0.9888592 0.1046694 0.8417851 0.80372740 0.6590069 0.4985587 0.6578783 0.9152619 0.4291725   0.10466936
# 2   b 0.1950439 0.8151934 0.6461509 0.4474437 0.79334595 0.1069735 0.2567427 0.1215491 0.4574306 0.3302786   0.10697354
# 3   c 0.9664587 0.2539684 0.5090904 0.9646670 0.35756312 0.1483839 0.4916694 0.5159349 0.9210739 0.7528325   0.14838386
# 4   d 0.6509055 0.6872308 0.7066286 0.1411871 0.05800106 0.9277570 0.1174759 0.3016811 0.2591188 0.8438981   0.05800106
# 5   e 0.3670719 0.8314290 0.8623137 0.7767125 0.56574614 0.4763697 0.5128062 0.7603278 0.3437781 0.0108724   0.47636970
# 6   f 0.6021140 0.9888592 0.1046694 0.8417851 0.80372740 0.6590069 0.4985587 0.6578783 0.9152619 0.4291725   0.10466936
# 7   g 0.1950439 0.8151934 0.6461509 0.4474437 0.79334595 0.1069735 0.2567427 0.1215491 0.4574306 0.3302786   0.10697354
# 8   h 0.9664587 0.2539684 0.5090904 0.9646670 0.35756312 0.1483839 0.4916694 0.5159349 0.9210739 0.7528325   0.14838386
# 9   i 0.6509055 0.6872308 0.7066286 0.1411871 0.05800106 0.9277570 0.1174759 0.3016811 0.2591188 0.8438981   0.05800106
# 10  j 0.3670719 0.8314290 0.8623137 0.7767125 0.56574614 0.4763697 0.5128062 0.7603278 0.3437781 0.0108724   0.47636970
编辑:正如@Moody_Mudskipper建议的那样,这可以包含在mutate中:
df %>% mutate(minval_X3_X8 = do.call(pmin, select(., X3:X8)))
# same output as above

但这种方法应该谨慎使用,因为它不尊重数据的分组。我经常使用下一个方法来查看我的函数在(可选地进行分组的)mutate中被调用时所“看到”的内容。这里的mymin函数并没有什么有用的作用,只是提供了一个中间的mutate浏览:
mymin <- function(...) { browser(); 1; }

例子:

df %>% rowwise() %>% mutate(minval_X3_X8 = do.call(mymin, select(., X3:X8)))
# Called from: (function (...) 
# {
#     browser()
#     1
# }) ...snip...
# Browse[1]> 
# debug at #1: [1] 1
# Browse[2]> 

如果我们查看函数的参数,我们会看到它提供了什么:
list(...)
# $X3
#  [1] 0.1046694 0.6461509 0.5090904 0.7066286 0.8623137 0.1046694 0.6461509 0.5090904 0.7066286
# [10] 0.8623137
# $X4
#  [1] 0.8417851 0.4474437 0.9646670 0.1411871 0.7767125 0.8417851 0.4474437 0.9646670 0.1411871
# [10] 0.7767125
# $X5
#  [1] 0.80372740 0.79334595 0.35756312 0.05800106 0.56574614 0.80372740 0.79334595 0.35756312
#  [9] 0.05800106 0.56574614
# $O6
#  [1] 0.6590069 0.1069735 0.1483839 0.9277570 0.4763697 0.6590069 0.1069735 0.1483839 0.9277570
# [10] 0.4763697
# $X7
#  [1] 0.4985587 0.2567427 0.4916694 0.1174759 0.5128062 0.4985587 0.2567427 0.4916694 0.1174759
# [10] 0.5128062
# $X8
#  [1] 0.6578783 0.1215491 0.5159349 0.3016811 0.7603278 0.6578783 0.1215491 0.5159349 0.3016811
# [10] 0.7603278

如果这是基于“按行”分组的,我预期会看到类似以下内容的结果,它只表示数据中的一行:
lapply(list(...), `[`, 1)
# $X3
# [1] 0.1046694
# $X4
# [1] 0.8417851
# $X5
# [1] 0.8037274
# $O6
# [1] 0.6590069
# $X7
# [1] 0.4985587
# $X8
# [1] 0.6578783

我相信这是最好的选择,如果我们更喜欢管道链,我们也可以执行 df %>% mutate(minval_X3_X8 = do.call(pmin, select(., X3:X8))) - moodymudskipper
感谢@Moody_Mudskipper,提出了很好的观点。已经编辑以包括此内容(加上警告)。 - r2evans

4

使用 tidyverse,我们可以做到:

1)使用reducepmin

library(tidyverse)
df %>%
  select(X3:X8) %>% 
  reduce(pmin) %>% 
  mutate(df, minval_X3_X8 = .)
# id        X1        X2        X3        X4         X5        X6        O6
#1   a 0.6021140 0.9888592 0.1046694 0.8417851 0.80372740 0.6590069 0.4985587
#2   b 0.1950439 0.8151934 0.6461509 0.4474437 0.79334595 0.1069735 0.2567427
#3   c 0.9664587 0.2539684 0.5090904 0.9646670 0.35756312 0.1483839 0.4916694
#4   d 0.6509055 0.6872308 0.7066286 0.1411871 0.05800106 0.9277570 0.1174759
#5   e 0.3670719 0.8314290 0.8623137 0.7767125 0.56574614 0.4763697 0.5128062
#6   f 0.6021140 0.9888592 0.1046694 0.8417851 0.80372740 0.6590069 0.4985587
#7   g 0.1950439 0.8151934 0.6461509 0.4474437 0.79334595 0.1069735 0.2567427
#8   h 0.9664587 0.2539684 0.5090904 0.9646670 0.35756312 0.1483839 0.4916694
#9   i 0.6509055 0.6872308 0.7066286 0.1411871 0.05800106 0.9277570 0.1174759
#10  j 0.3670719 0.8314290 0.8623137 0.7767125 0.56574614 0.4763697 0.5128062
#          X8        X9       X10 minval_X3_X8
#1  0.6578783 0.9152619 0.4291725   0.10466936
#2  0.1215491 0.4574306 0.3302786   0.10697354
#3  0.5159349 0.9210739 0.7528325   0.14838386
#4  0.3016811 0.2591188 0.8438981   0.05800106
#5  0.7603278 0.3437781 0.0108724   0.47636970
#6  0.6578783 0.9152619 0.4291725   0.10466936
#7  0.1215491 0.4574306 0.3302786   0.10697354
#8  0.5159349 0.9210739 0.7528325   0.14838386
#9  0.3016811 0.2591188 0.8438981   0.05800106
#10 0.7603278 0.3437781 0.0108724   0.47636970

2) 或将列名转换为符号进行评估

df %>% 
   mutate(minval_X3_X8 = pmin(!!! rlang::syms(names(.)[3:8])))

1
第二行可以使用 do.call(pmin,.) %>% 或者 invoke(pmin,.) %>% 来提高效率(类似于 r2evans 的解决方案)。 - moodymudskipper

3
关于你的问题(1),问题中的代码与以下代码的作用相同:
df %>% rowwise() %>% mutate(minval_X3_X8 = min(X3))

或者只是
df %>% rowwise() %>% mutate(minval_X3_X8 = X3)

对于下面问题(1)中的问题(2),我们重新调整您的解决方案,以使其正常工作,并在此基础上提供一些其他的dplyr和base解决方案。在下面的解决方案中,我们已经展示了minmax的结果。通过向mutatesummarize添加参数或扩展aggregate函数,可以轻松地将它们扩展到其他统计数据,如meansdmedian等。

请注意,下面的解决方案都使用简单的minmax等,这使得将其扩展到其他统计数据更加容易。

使用do来修改您的代码。在do中,一个点将指向当前组,也就是当前行,但它将是一个列表,因此需要将其转换回数据框。请注意,我们在do中使用了{...}来防止{...}内的点引用当前行作为列表,而是引用data.frame(.)

df %>% 
   rowwise %>% 
   do(as.data.frame(.) %>% { 
      subs <- select(., X3:X8)
      mutate(., Min = subs %>% min,
                Max = subs %>% max) 
      } ) %>%
   ungroup

提供:

# A tibble: 10 x 13
   id       X1    X2    X3    X4     X5    X6    O6    X8    X9    X10    Min   Max
 * <fct> <dbl> <dbl> <dbl> <dbl>  <dbl> <dbl> <dbl> <dbl> <dbl>  <dbl>  <dbl> <dbl>
 1 a     0.602 0.989 0.105 0.842 0.804  0.659 0.499 0.658 0.915 0.429  0.105  0.842
 2 b     0.195 0.815 0.646 0.447 0.793  0.107 0.257 0.122 0.457 0.330  0.107  0.793
 3 c     0.966 0.254 0.509 0.965 0.358  0.148 0.492 0.516 0.921 0.753  0.148  0.965
 4 d     0.651 0.687 0.707 0.141 0.0580 0.928 0.117 0.302 0.259 0.844  0.0580 0.928
 5 e     0.367 0.831 0.862 0.777 0.566  0.476 0.513 0.760 0.344 0.0109 0.476  0.862
 6 f     0.602 0.989 0.105 0.842 0.804  0.659 0.499 0.658 0.915 0.429  0.105  0.842
 7 g     0.195 0.815 0.646 0.447 0.793  0.107 0.257 0.122 0.457 0.330  0.107  0.793
 8 h     0.966 0.254 0.509 0.965 0.358  0.148 0.492 0.516 0.921 0.753  0.148  0.965
 9 i     0.651 0.687 0.707 0.141 0.0580 0.928 0.117 0.302 0.259 0.844  0.0580 0.928
10 j     0.367 0.831 0.862 0.777 0.566  0.476 0.513 0.760 0.344 0.0109 0.476  0.862

2)应用使用apply

df %>% {
  Apply <- function(fun) select(., X3:X8) %>% apply(1, fun)
  mutate(., Min = Apply(min), Max = Apply(max))
}

2a) 在基本R中或仅使用基本R:

Apply <- function(fun) apply(subset(df, select = X3:X8), 1, fun)
transform(df, Min = Apply(min), Max = Apply(max))

3) dplyr/tidyr 另一种可能性是使用 dplyr 和 tidyr 将 df 转换为长格式,以长格式执行计算,然后再连接回 df

library(dplyr)
library(tidyr)

df %>%
   left_join({
     gather(., key, value, -id) %>%
     filter(between(key, "X3", "X8")) %>%
     group_by(id) %>%
     summarize(Min = min(value), Max = max(value)) %>%
     ungroup
   })

3a) 基础R (3) 可以使用R基础语言中的reshape创建长格式数据框,使用subset将其缩小至X3:X8,并使用merge执行连接操作。

long <- reshape(df, dir = "long", varying = list(names(df)[-1]), 
  times = names(df)[-1], v.names = "min")
subs <- subset(long, time >= "X3" & time <= "X8")
merge(df, aggregate(min ~ id, subs, function(x) c(Min = min(x), Max = max(x))))

4) dplyr/purrr这个跟(2)很相似,只不过我们使用的是purrr::pmap_dbl而不是apply

library(dplyr)
library(purrr)

df %>% {
  Pmap <- function(fun) select(., X3:X8) %>% pmap_dbl(~ fun(c(...)))
  mutate(., Min = Pmap(min), Max = Pmap(max))
}

更新

已经进行了一些改进并增加了额外的解决方案。


我不知道subset(..., select=)允许使用冒号范围,太棒了。 - r2evans

2

您也可以使用purrr::maptranspose

来实现此功能。

df %>% mutate(minval_X3_X8 = map(transpose(select(., X3:X8)), ~min(as.numeric(.x))))
#id        X1        X2        X3        X4         X5        X6        O6
#1   a 0.6021140 0.9888592 0.1046694 0.8417851 0.80372740 0.6590069 0.4985587
#2   b 0.1950439 0.8151934 0.6461509 0.4474437 0.79334595 0.1069735 0.2567427
#3   c 0.9664587 0.2539684 0.5090904 0.9646670 0.35756312 0.1483839 0.4916694
#4   d 0.6509055 0.6872308 0.7066286 0.1411871 0.05800106 0.9277570 0.1174759
#5   e 0.3670719 0.8314290 0.8623137 0.7767125 0.56574614 0.4763697 0.5128062
#6   f 0.6021140 0.9888592 0.1046694 0.8417851 0.80372740 0.6590069 0.4985587
#7   g 0.1950439 0.8151934 0.6461509 0.4474437 0.79334595 0.1069735 0.2567427
#8   h 0.9664587 0.2539684 0.5090904 0.9646670 0.35756312 0.1483839 0.4916694
#9   i 0.6509055 0.6872308 0.7066286 0.1411871 0.05800106 0.9277570 0.1174759
#10  j 0.3670719 0.8314290 0.8623137 0.7767125 0.56574614 0.4763697 0.5128062
#       X8        X9       X10 minval_X3_X8
#1  0.6578783 0.9152619 0.4291725    0.1046694
#2  0.1215491 0.4574306 0.3302786    0.1069735
#3  0.5159349 0.9210739 0.7528325    0.1483839
#4  0.3016811 0.2591188 0.8438981   0.05800106
#5  0.7603278 0.3437781 0.0108724    0.4763697
#6  0.6578783 0.9152619 0.4291725    0.1046694
#7  0.1215491 0.4574306 0.3302786    0.1069735
#8  0.5159349 0.9210739 0.7528325    0.1483839
#9  0.3016811 0.2591188 0.8438981   0.05800106
#10 0.7603278 0.3437781 0.0108724    0.4763697 

哦,我喜欢transpose,谢谢你演示它!(由于map的工作方式,此方法隐式地尊重了rowwise,这是一个额外的奖励。) - r2evans
我非常喜欢这个解决方案的简洁性。不幸的是,在我的环境中它无法工作...有什么提示吗?我收到了“Error in mutate_impl(.data, dots) : Evaluation error: invalid 'type' (list) of argument.”的错误信息。 - Yvan
@Yvan 你说得对,我很困惑;-) 即使(我认为)它之前在另一台机器上运行过,我仍然会遇到相同的错误。无论如何,我已经修复了这个错误,也许你可以再试一次,我们需要一个额外的 as.numeric(来折叠从 transpose 中嵌套的 list 结构)。虽然不是那么整洁,但仍然非常简短。 - Maurits Evers

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