dplyr 按照某些列进行逐行操作

8

什么是使用dplyr的方法来对一些列应用rowwise函数。例如,我想抓取所有V,列,并基于行总和将它们转换为百分比。我展示了如何在base中实现它。那么在dplyr链中呢?最好也能以data.table形式呈现(尽管在这里偏向于dplyr解决方案)。

x <- data.frame(A=LETTERS[1:5], as.data.frame(matrix(sample(0:5, 25, T), ncol=5)))

data.frame(x[1], x[-1]/rowSums(x[-1]))


##   A        V1        V2        V3         V4         V5
## 1 A 0.1428571 0.2142857 0.2142857 0.35714286 0.07142857
## 2 B 0.2000000 0.2000000 0.1500000 0.20000000 0.25000000
## 3 C 0.3571429 0.2857143 0.0000000 0.07142857 0.28571429
## 4 D 0.1904762 0.2380952 0.1904762 0.23809524 0.14285714
## 5 E 0.2000000 0.2500000 0.1500000 0.25000000 0.15000000

library(dplyr)

props <- function(x) round(x/sum(x), 2)

# does not work
x %>%
    rowwise()
    mutate(props(matches("^.{2}$")))

1
也许是 x %>% rowwise() %>% select(matches("^.{2}$")) %>% props %>% cbind(x[1], .)?但后半部分并不完全是dplyr。 - Rich Scriven
2
我不是dplyr专家,但你不能在dplyr中使用rowSums吗?像这样:props <- function(x, y) round(x/y, 2) ; x %>% mutate(Total = rowSums(.[-1])) %>% mutate_each(funs(./Total), -c(A, Total))。虽然rowSumsrowwise都可能效率低下。如果没有NA,我会选择Reduce(`+`, .[-1])) - David Arenburg
Tyler,你看到这个了吗?我想知道你是想要一个通用的逐行解决方案还是特定的按行求和? - David Arenburg
1
@Frank 不用了,我会移除它。虽然这样可以让任何人直接运行代码,而不需要输入库等内容来获取 dplyr。 - Tyler Rinker
2
一个“知名的data.table专家”,在SO dplyr答案排行榜上靠近hadley :) http://stackoverflow.com/tags/dplyr/topusers - Frank
显示剩余2条评论
3个回答

7
在data.table中,您可以执行以下操作:
library(data.table)
setDT(x)

x[, grep("^V",names(DT)) := .SD/Reduce(`+`, .SD), .SDcols = V1:V5]

   A         V1        V2        V3         V4         V5
1: A 0.28571429 0.0000000 0.2857143 0.07142857 0.35714286
2: B 0.23076923 0.2307692 0.3076923 0.15384615 0.07692308
3: C 0.44444444 0.0000000 0.4444444 0.00000000 0.11111111
4: D 0.07142857 0.3571429 0.1428571 0.07142857 0.35714286
5: E 0.00000000 0.2222222 0.3333333 0.44444444 0.00000000

忽略NA值计算分母,我想rowSums是一个选项,不过它会将.SD强制转换为矩阵作为中间步骤。


没关系,我并没有发明Reduce(`+`,...)。我只是在想这是否是一个重复的问题? - David Arenburg
1
虽然不完全相同,但https://dev59.com/mlsW5IYBdhLWcg3wKkk0#35306944与此相关。 - thelatemail
@DavidArenburg 看起来应该是重复的,但我找不到一个明显的标题。 - Tyler Rinker
3
@thelatemail 我们一段时间以来一直在使用这个 Reduce(`+`, .SD) 方法(参考链接:https://dev59.com/povda4cB1Zd3GeqPUhJo#30354129)。以下是2014年akrun的回答(参考链接:http://stackoverflow.com/questions/26305233/how-can-i-specify-columns-in-r-to-be-used-in-matches-without-listing-each-indiv/26305482#26305482)。 - David Arenburg
1
没想到你可以在 := 的左侧传递列号... 我本来会使用 value = TRUE - MichaelChirico

6

您可以将spreadgather结合起来,得到如下单一管道:

x <- data.frame(A=LETTERS[1:5], as.data.frame(matrix(sample(0:5, 25, T), ncol=5)))

y <- x %>% 
        gather(V, val, -A) %>% 
        group_by(A) %>% 
        mutate(perc = val / sum(val)) %>% 
        select(-val) %>%
        spread(V, perc)

使用整洁数据,可以轻松地获得任何分组总和(行、列或任何嵌套索引级别),并计算百分比。 spreadgather 可以将您带到和从您的输入数据格式中。

啊,这很有道理。这是一个“为什么我没想到”的时刻。 - Tyler Rinker
做这样的按行分组,我猜随着数据的增长,速度会很快变慢。 - eddi
@eddi 我还没有在大数据上测试过这个。dplyr 可能不是最好的方式。我怀疑它会比 OP 显示的 data.frame 通过 rowSums 进行缩放更慢。在整洁的数据格式中,可以在执行 group_by(A) 之前进行 arrange(A),以便按顺序处理每个组的数据。 - TemplateRex

0
另一个“tidyverse”解决方案是在mutate中使用select。例如:
library(tidyverse)

x <- data.frame(A=LETTERS[1:5], as.data.frame(matrix(sample(0:5, 25, T), ncol=5)))

x %>% 
  mutate(row_counts = select_if(., is.numeric) %>% rowSums()) %>% 
  mutate_at(vars(contains("V")), funs(./row_counts)) %>% 
  select(-row_counts)
#>   A        V1         V2        V3        V4        V5
#> 1 A 0.0000000 0.14285714 0.1428571 0.5714286 0.1428571
#> 2 B 0.0000000 0.62500000 0.1250000 0.1250000 0.1250000
#> 3 C 0.2222222 0.11111111 0.2222222 0.1111111 0.3333333
#> 4 D 0.3000000 0.50000000 0.1000000 0.1000000 0.0000000
#> 5 E 0.3333333 0.06666667 0.1333333 0.3333333 0.1333333

reprex package (v0.2.1)于2019年02月16日创建


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