Dplyr & purrr с динамическими именами столбцов, выбор и копирование по группе

У меня есть следующий код, в котором мне нужно получить имена столбцов из переменной, а затем выполнить операцию выборочно для строк, используя указанный столбец в этой операции. Вот мой простой пример создания столбца res для соответствия столбцу target:

library(tidyverse)

tst <- tibble(grp = c("a","a","b","b","c","c"), a = rep(2,6), b = rep(4,6), 
              c = rep(8,6), target = c(2,2,4,4,8,8), res = rep(0,6))

# create list of columns to iterate over
lst <-  unique(tst$grp)

# loop over each column, creating a dummy column with 
# the copied value in the matching rows, zeroes elsewhere
for(g in lst) {

  tst <- tst %>% 
    mutate(!!paste("res", g, sep="_") := ifelse(grp == g, !!rlang::sym(g),0)) %>% 
    select(!!paste("res", g, sep="_")) %>% 
    cbind(tst)
}

# combine the dummy columns by rowSum
res <- tst %>% select(starts_with("res_")) %>% mutate(res = rowSums(.)) %>% 
select(res)

# tidy up the output, result matches the target
tst <- tst %>% select(grp, a, b, c, target) %>% cbind(res)

tst

  grp a b c target res
1   a 2 4 8      2   2
2   a 2 4 8      2   2
3   b 2 4 8      4   4
4   b 2 4 8      4   4
5   c 2 4 8      8   8
6   c 2 4 8      8   8

Я применил итеративный подход, перебирая уникальные переменные в grp столбце, создавая временные столбцы, а затем rowSum() просматривая их, чтобы получить окончательный результат. Неуклюже, но в конце концов добрался до цели.

Я уверен, что есть более элегантный способ сделать это с одним из семейства map от purrr. Может ли кто-нибудь показать мне, как я могу сделать это без цикла, используя purrr? Я действительно изо всех сил пытался заставить бит имени динамического столбца работать с использованием этого подхода. Заранее спасибо.


person Scott Simpson    schedule 05.06.2018    source источник


Ответы (4)


Вы можете использовать imap, который перебирает значения столбцов и их имена. Значения столбца - это значения grp, имена - это просто последовательность 1,...,6.

Кроме того, вы должны предоставить сам фрейм данных в качестве дополнительного аргумента (от df= до imap, который он пересылает своему аргументу функции. Итого:

tst %>% 
  mutate(res = purrr::imap_dbl(grp, df = ., 
    .f = function(g, i, df) df[i,g][[1]] # [[1]] turns the result from tibble into a double
  )) 

Изменить: я приурочил это решение к более крупной таблице:

tst <- tst[sample(nrow(tst), 50000, TRUE),]

и на это уходит около 50сек.

person akraf    schedule 05.06.2018
comment
Спасибо, вот что мне было нужно - person Scott Simpson; 05.06.2018
comment
@ScottSimpson Если у вас большая таблица (›10000 строк), вы можете попробовать чистое решение R, которое быстрее в 5000 раз. - person akraf; 05.06.2018

То, что не требует написания цикла

library(tidyverse)

tst <- tibble(grp = c("a","a","b","b","c","c"), a = rep(2,6), b = rep(4,6), 
              c = rep(8,6), target = c(2,2,4,4,8,8), res = rep(0,6))

tst %>% 
  mutate(res = 
           case_when(
             grp == "a" ~ a,
             grp == "b" ~ b,
             grp == "c" ~ c
           ))

# A tibble: 6 x 6
  grp       a     b     c target   res
  <chr> <dbl> <dbl> <dbl>  <dbl> <dbl>
1 a         2     4     8      2     2
2 a         2     4     8      2     2
3 b         2     4     8      4     4
4 b         2     4     8      4     4
5 c         2     4     8      8     8
6 c         2     4     8      8     8

Примечание. Вместо ~ a при необходимости можно использовать собственную формулу.
Для получения дополнительной помощи см. ?case_when

person Vasim    schedule 05.06.2018

Это базовое решение R, которое тоже не длиннее:

# Save all source columns in a matrix. This enables indexing by another matrix
x <- as.matrix(tst[, unique(tst$grp)])
# Matrix of (row, column) pairs to extract from x
i <- cbind(seq_len(nrow(tst)), match(tst$grp, colnames(x)))
tst$res <- x[i]

Изменить: Истекшее время для большей таблицы:

tst <- tst[sample(nrow(tst), 50000, TRUE), ]

0.008s -- 0.015s

person akraf    schedule 05.06.2018

Возможно:

tst %>% 
  mutate(res = sapply(seq(nrow(tst)), function(x) tst[x,as.character(tst$grp[x])]))


# A tibble: 6 x 6
    grp     a     b     c target   res
  <chr> <dbl> <dbl> <dbl>  <dbl> <dbl>
1     a     2     4     8      2     2
2     a     2     4     8      2     2
3     b     2     4     8      4     4
4     b     2     4     8      4     4
5     c     2     4     8      8     8
6     c     2     4     8      8     8
person Lennyy    schedule 05.06.2018
comment
Спасибо, очень полезно - person Scott Simpson; 05.06.2018