Отменить список / разделить столбец списка на несколько столбцов

Я знаю, что вопрос об отключении столбца списка во фрейме данных поднимался и отвечал несколько раз. Однако здесь потенциально 237. проблема такого рода.

У меня есть следующие данные:

set.seed(666)
dat <- data.frame(sysRespNum = c(1,2,3,4,5,6),
                  product1   = sqrt(rnorm(6, 20, 5)^2),
                  product2   = sqrt(rnorm(6, 20, 5)^2),
                  product3   = sqrt(rnorm(6, 20, 5)^2))

данные:

  sysRespNum  product1 product2 product3
1          1 23.766555 13.46907 24.32327
2          2 30.071773 15.98740 11.39922
3          3 18.224328 11.03880 20.67063
4          4 30.140839 19.78984 19.62087
5          5  8.915628 30.75021 24.29150
6          6 23.791981 11.14885 21.72450

Теперь я хочу вычислить долю каждого продукта в сумме всех продуктов, поэтому я хочу вычислить product1/sum(my three products), затем то же самое для продукта 2 и 3. Итак, я ожидаю трех новых столбцов.

Я пробовал следующее:

library(tidyverse)    
dat %>%
  mutate(sum_Product = apply(across(-sysRespNum), 1, function(x) list(sum_Product = x/sum(x))))

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

Теперь моя проблема в том, что трудно отключить столбец списка sum_Product. unnest_wider не работает, столбец sum_Product по-прежнему является списком.

Так что единственное, что сработало для меня, это

полный код:

dat %>%
  mutate(sum_Product = apply(across(-sysRespNum), 1, function(x) data.frame(sum_Product = x/sum(x)))) %>%
  unnest(cols = everything()) %>%
  mutate(product = rep(1:3, nrow(.)/3)) %>%
  pivot_wider(values_from = sum_Product,
              names_from = product,
              names_prefix = "share_product")

что дает правильный результат:

# A tibble: 6 x 7
  sysRespNum product1 product2 product3 share_product1 share_product2
       <dbl>    <dbl>    <dbl>    <dbl>          <dbl>          <dbl>
1          1    23.8      13.5     24.3          0.386          0.219
2          2    30.1      16.0     11.4          0.523          0.278
3          3    18.2      11.0     20.7          0.365          0.221
4          4    30.1      19.8     19.6          0.433          0.285
5          5     8.92     30.8     24.3          0.139          0.481
6          6    23.8      11.1     21.7          0.420          0.197
# … with 1 more variable: share_product3 <dbl>

Однако кажется излишне сложным разложить все, а затем изменить форму с помощью pivot_wider.

Итак, а) есть ли более элегантный способ вычисления моих общих переменных и б) есть ли более элегантный / короче / менее подробный способ преобразования столбца списка в несколько векторных столбцов?


person deschen    schedule 08.12.2020    source источник
comment
Это не tidyverse, но prop.table должно это делать vars <- names(dat)[startsWith(names(dat), "product")]; dat[paste0("share_",vars)] <- prop.table(as.matrix(dat[vars]), 1)   -  person thelatemail    schedule 09.12.2020


Ответы (3)


Это проще сделать rowSums, т.е. разделить "product1" на rowSums в столбцах, которые начинаются с ключевого слова "product". Вместо того, чтобы делать rowwise с c_across, он векторизован и тоже должен быть быстрым.

library(dplyr)
dat %>%
    mutate(sum_product = product1/rowSums(select(., starts_with('product'))))

ПРИМЕЧАНИЕ. Существует сочетание кода base R (apply) и опции tidyverse с across, что не кажется оптимальным способом


Если нам нужно сделать это для всех столбцов 'product', сначала создайте столбец sum с mutate, а затем используйте across в столбцах, которые начинаются с 'product', чтобы разделить столбец на 'Sum_col'.

dat %>%
     mutate(Sum_col = rowSums(select(., starts_with('product'))),
           across(starts_with('product'),
        ~ ./Sum_col, .names = '{.col}_sum_product')) %>%
     select(-Sum_col)

-выход

#ysRespNum  product1 product2 product3 product1_sum_product product2_sum_product product3_sum_product
#1          1 23.766555 13.46907 24.32327            0.3860783            0.2187998            0.3951219
#2          2 30.071773 15.98740 11.39922            0.5233660            0.2782431            0.1983909
#3          3 18.224328 11.03880 20.67063            0.3649701            0.2210688            0.4139610
#4          4 30.140839 19.78984 19.62087            0.4333597            0.2845348            0.2821054
#5          5  8.915628 30.75021 24.29150            0.1393996            0.4807925            0.3798079
#6          6 23.791981 11.14885 21.72450            0.4198684            0.1967490            0.3833826

Или используя base R

nm1 <- startsWith(names(dat), 'product')
dat[paste0('sum_product', seq_along(nm1))] <- dat[nm1]/rowSums(dat[nm1])
person akrun    schedule 08.12.2020
comment
Да, но это дает мне долю только для продукта1, но не для продукта2 и 3. И я хочу избежать жесткого кодирования каждого из трех продуктов, потому что в реальной жизни у меня разное количество столбцов продуктов в разных случаях использования. - person deschen; 09.12.2020
comment
В противном случае я в целом согласен с быстродействием rowSums. - person deschen; 09.12.2020
comment
@deschen Возможно обновление вам поможет - person akrun; 09.12.2020
comment
Спасибо, акрун. Да, я тоже думал об этом решении (т.е. сначала создав временный столбец с суммой), но интересовался, есть ли прямой способ без этого шага (т.е. в моем сообщении я как бы исключил этот вариант временной суммы). - person deschen; 09.12.2020
comment
@deschen Лучше создать временный столбец, чем делать rowSums n раз - person akrun; 09.12.2020
comment
Повторное использование apply: я знаю, что rowSums выполняется довольно быстро, однако иногда мне нужно выполнить аналогичные вычисления, например, mean или sd или что-то еще, то есть с функциями, в которых # нет выделенного варианта строки. Итак, здесь я нашел эту прикладную версию, которая в основном работает с любой из таких функций, поэтому мой код всегда будет выглядеть одинаково. - person deschen; 09.12.2020
comment
@deschen ваш код - это, по сути, rowwise из base R, что также можно сделать в tidyverse, но это будет значительно медленнее. - person akrun; 09.12.2020
comment
Это правда. Думаю, я сначала убедился, что нужно создать rowSum. В любом случае, возвращаясь к моему первоначальному вопросу: есть ли простой способ отключить этот столбец списка без необходимости слишком большого количества кода (например, pivot_wider)? - person deschen; 09.12.2020
comment
@deschen вы можете использовать unnest_wider, т.е. dat %>% mutate(sum_Product = apply(across(-sysRespNum), 1, function(x) list(sum_Product = x/sum(x)))) %>% unnest(c(sum_Product)) %>% unnest_wider(c(sum_Product), names_repair = 'unique') - person akrun; 09.12.2020

Я предполагаю, что следующий базовый код R должен работать для вас

cbind(
  dat,
  setNames(dat[-1] / rowSums(dat[-1]), paste0("share_product", seq_along(dat[-1])))
)

который дает

  sysRespNum  product1 product2 product3 share_product1 share_product2
1          1 23.766555 13.46907 24.32327      0.3860783      0.2187998
2          2 30.071773 15.98740 11.39922      0.5233660      0.2782431
3          3 18.224328 11.03880 20.67063      0.3649701      0.2210688
4          4 30.140839 19.78984 19.62087      0.4333597      0.2845348
5          5  8.915628 30.75021 24.29150      0.1393996      0.4807925
6          6 23.791981 11.14885 21.72450      0.4198684      0.1967490
  share_product3
1      0.3951219
2      0.1983909
3      0.4139610
4      0.2821054
5      0.3798079
6      0.3833826
person ThomasIsCoding    schedule 08.12.2020

Старый добрый простой базовый R

rdat <- dat[-1]
rdat <- rdat/rowSums(rdat)
colnames(rdat) <- paste0("share_", colnames(rdat))
cbind(dat, rdat)

Который дает:

  sysRespNum  product1 product2 product3 share_sum_product1 share_sum_product2
1          1 23.766555 13.46907 24.32327          0.3860783          0.2187998
2          2 30.071773 15.98740 11.39922          0.5233660          0.2782431
3          3 18.224328 11.03880 20.67063          0.3649701          0.2210688
4          4 30.140839 19.78984 19.62087          0.4333597          0.2845348
5          5  8.915628 30.75021 24.29150          0.1393996          0.4807925
6          6 23.791981 11.14885 21.72450          0.4198684          0.1967490
  share_sum_product3
1          0.3951219
2          0.1983909
3          0.4139610
4          0.2821054
5          0.3798079
6          0.3833826

person Gwang-Jin Kim    schedule 08.12.2020