R повторите в столбце на основе значения в строке

#r #dataframe #repeat #long-integer

Вопрос:

У меня есть фрейм данных, подобный следующему:

 Name    School   Weight Days
Antoine Bach     0.03   5
Antoine Ken      0.02   7
Barbara Franklin 0.04   3
 

Я хотел бы получить результат, подобный следующему:

 Name    School   1    2    3    4    5    6    7
Antoine Bach     0.03 0.03 0.03 0.03 0.03 NA   NA
Antoine Ken      0.02 0.02 0.02 0.02 0.02 0.02 0.02
Barbara Franklin 0.04 0.04 0.04 NA   NA   NA   NA
 

Воспроизводимые Выборочные Данные:

 df <- tribble(
  ~Name,    ~School,   ~Weight, ~Days,
  "Antoine", "Bach",     0.03,   5,
  "Antoine", "Ken",      0.02,   7,
  "Barbara", "Franklin", 0.04,   3
)

 

Ответ №1:

Используя data.table, вы можете создать длинную версию, rep съев Weight значение Days несколько раз для каждой строки, а затем dcast перейдя в широкий формат с rowid новой переменной в качестве столбца.

 library(data.table)
setDT(df)

dcast(df[, .(rep(Weight, Days)), .(Name, School)], 
      Name   School ~ rowid(V1))

# Name   School    1    2    3    4    5    6    7
# 1: Antoine     Bach 0.03 0.03 0.03 0.03 0.03   NA   NA
# 2: Antoine      Ken 0.02 0.02 0.02 0.02 0.02 0.02 0.02
# 3: Barbara Franklin 0.04 0.04 0.04   NA   NA   NA   NA
 

Вы также можете rep Weight указать количество повторений Days , а затем NA повторить их достаточно раз, чтобы завершить строку.

 max_days <- max(df$Days) 

df[, as.list(rep(c(Weight, NA), c(Days, max_days - Days))), 
   .(Name, School)]

# Name   School   V1   V2   V3   V4   V5   V6   V7
# 1: Antoine     Bach 0.03 0.03 0.03 0.03 0.03   NA   NA
# 2: Antoine      Ken 0.02 0.02 0.02 0.02 0.02 0.02 0.02
# 3: Barbara Franklin 0.04 0.04 0.04   NA   NA   NA   NA
 

Комментарии:

1. Очень хорошее решение. У меня никогда не было возможности ознакомиться с data.table пакетом. У вас есть какие-либо предложения о том, с чего начать? Например, документация или, возможно, другой источник. Заранее спасибо.

2. Я поддерживаю вышеприведенную просьбу. Даже мне еще предстоит найти учебник/книгу, посвященную этому пакету. Единственное, что я знаю, это то, что это очень быстро

3. Вингетты на их странице и учебник Фрэнка Нарфа по R , в котором используются данные.таблица, оба хороши

4. Этот powerpoint из выступления одного из создателей хорош для ознакомления с основами. Существует также этот учебник , в котором описываются методы для dplyr и data.table бок о бок

5. @IceCreamToucan Действительно большое вам спасибо. Я не знаю, как тебя благодарить. Я обязательно проверю их все. Желаю вам всего наилучшего.

Ответ №2:

Вы можете использовать pmap_dfr , чтобы применить функцию к строкам, а затем связать полученный список строк в объект tibble. Функция сопоставит аргументы с именами столбцов, остальные значения строк будут записаны многоточием ... .

 library(purrr)
library(dplyr)

pmap_dfr(df, function(Weight, Days, ...) c(..., setNames(rep(Weight, Days), 1:Days))) %>% 
  mutate(across(3:last_col(), as.numeric))
 

Поскольку векторы являются атомарными в R c() , все в строке должно быть символьным. Таким образом, мутация преобразует вновь созданные столбцы обратно в числовые.

setNames используется для именования вновь созданных столбцов, которые необходимо связать по строкам.

Выход

   Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
  <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA   
 

Примечание: pmap_dfr это из purrr упаковки, и mutate , across , и last_col все из dplyr .

Как это работает

При использовании pmap вышеописанным способом аргументы именованной функции будут сопоставлены столбцам с тем же именем. Weights Days Аргументы функции So и as сопоставляются тем столбцам с одинаковыми именами в каждой строке.

... Собирает оставшиеся столбцы, которые все еще передаются функции, но не используются (по имени) в функции. По сути, многоточие собирает Name и School в вашем случае.

Поскольку Name и School у них уже есть имена, они передаются в c() первую очередь для поддержания порядка столбцов. Кроме того, мы объединяем другие значения и даем им имена. Вывод для одной строки, то это:

      Name    School         1         2         3         4         5         6 
"Antoine"    "Bach"    "0.03"    "0.03"    "0.03"    "0.03"    "0.03"        NA 
        7 
       NA 
 

Результатом pmap является список. _dfr является определенной функцией для привязки строк (отсюда r ) этих элементов списка в фрейм данных/тиббл (отсюда df ).

Комментарии:

1. Мне очень понравилось ваше решение. На самом деле функции from purrr всегда являются моим первым выбором для работы по строкам, и я pmap часто использую c(...) их для сбора нескольких аргументов. Но здесь я не могу полностью понять, как мы можем передавать функции c(...) и как это получается так, как должно быть. Не могли бы вы, пожалуйста, немного объяснить мне это, мне очень любопытно знать.

2. @AnoushiravanR Конечно, никаких проблем. Я обновил это решение более подробно. Надеюсь, это даст больше понимания того, как это работает. Для получения дополнительной информации и другого примера см. Последовательность примеров в документации ?pmap_dfr в нижней части страницы, начинающуюся с комментария «# Использовать ... для поглощения неиспользуемых компонентов списка ввода .l».

3. Большое вам спасибо за ваше подробное объяснение. Мне просто любопытно узнать, почему следующий код не работает. Поскольку я просто заменяю анонимную функцию формулой: df %>% pmap_dfr(., ~ c(..., setNames(rep(Weight, Days), 1:Days)))

4. Используйте .x$ перед именами столбцов в невидимой функции, и это должно сработать. Замените эллипсы аналогичным способом

5. @AnilGoyal Большое тебе спасибо. Я действительно ценю наши обсуждения. Они очень полезны и богаты.

Ответ №3:

Одно tidyverse решение.

  • Сначала мы tidyr::nest две колонны. Результирующий столбец будет столбцом списка с именем d dummy.
  • затем мы мутируем d в вектор weights до days времени, используя rep функцию. Эта итерация выполняется с использованием purrr::map . Примечание: map_dbl на данном этапе это не требуется, так как на следующем шаге это будет не нужно.
  • мы также заключаем этот аргумент в setNames так, чтобы d он мутировал в именованный список (имена, как и ожидалось). Для имен seq используется функция.
  • наконец, мы tidyr::unnest_wider d разделяем столбец на столбцы с именами, уже сохраненными в списке на предыдущем шаге
 library(dplyr)
library(tidyr)
library(purrr)

df %>% nest(d = c(Weight, Days)) %>%
  mutate(d = map(d, ~setNames( rep(.x$Weight, .x$Days), seq(1, .x$Days, 1)))) %>%
  unnest_wider(d)

# A tibble: 3 x 9
  Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
  <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA 
 

Комментарии:

1. Анил, я только что нашел очень интересную идею. Мы можем использовать функцию map в вызове mutate , не называя имя выходного столбца. Я уже знал, что мы можем управлять именованием с помощью списка map_dfr , map_dfc но эта идея была для меня совершенно новой, и я нашел ее весьма полезной.

2. Не могли бы вы, пожалуйста, объяснить немного подробнее?

3. Большое вам спасибо, вы тоже наверняка заслуживаете повышенного голоса.

Ответ №4:

Вы можете использовать следующий код для получения требуемых выходных данных:

 library(dplyr)
library(tidyr)

df %>% 
  select(Weight, Days) %>%
  uncount(Days, .remove = FALSE) %>%
  group_by(Days) %>%
  mutate(id = row_number()) %>%
  pivot_wider(Days, names_from = id, values_from = Weight) %>%
  right_join(df, by = "Days") %>%
  relocate(Name, School) %>%
  ungroup() %>%
  select(-c(Weight, Days))

# A tibble: 3 x 9
  Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
  <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA 

 

Данные:

 df <- tribble(
  ~Name,    ~School,   ~Weight, ~Days,
  "Antoine", "Bach",     0.03,   5,
  "Antoine", "Ken",      0.02,   7,
  "Barbara", "Franklin", 0.04,   3
)

 

Обновленный
Поскольку наши дорогие друзья по праву предложили использовать pmap и map из purrr пакета, вот еще один вариант, думаю, было бы здорово узнать:

 library(purrr)

df %>%
  mutate(map2_dfr(Weight, Days, ~ set_names(rep(.x, .y), 1:.y))) %>%
  select(-c(Weight, Days))

# A tibble: 3 x 9
  Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
  <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA 

 

Ответ №5:

Мне нравится tidyr::uncount делать x количество копий каждой строки. Мы можем поворачиваться дольше, не считать, а затем снова поворачиваться шире.

 library(tidyr)
my_data %>%
  pivot_longer(Weight) %>%
  uncount(Days, .id = "colnum") %>%
  dplyr::select(-name) %>%
  pivot_wider(names_from = colnum, values_from = value)


# A tibble: 3 x 9
  Name    School     `1`   `2`   `3`   `4`   `5`   `6`   `7`
  <chr>   <chr>    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 Antoine Bach      0.03  0.03  0.03  0.03  0.03 NA    NA   
2 Antoine Ken       0.02  0.02  0.02  0.02  0.02  0.02  0.02
3 Barbara Franklin  0.04  0.04  0.04 NA    NA    NA    NA   
 

Комментарии:

1. подсчет действительно потрясающая функция