Добавить строки в фрейм данных по шаблону - PullRequest
0 голосов
/ 15 марта 2020

У меня есть фрейм данных с участниками (ID), которые ответили на несколько анкет последовательно (каждая строка является анкетой). Все они начинали с «общего» вопросника, а затем отвечали на пары «до» и «после» вопросника (столбец «Заказ»). Столбец «Значение» показывает пример данных (есть еще много столбцов с данными и еще много участников). Количество ответивших «пар» у участников разное.

    ID   Order     Value
1   1    general     1
2   1    pre         3
3   1    post        4
4   1    post        7
5   1    pre         0
6   1    post       10
7   2    general     1
8   2    post        0
9   2    pre        12
10  3    general    12
11  3    pre         3
12  3    post        4
13  3    pre         6
14  3    pre         8

Пример данных:

df1 <- data.frame("ID" = as.factor(c('1', '1', '1', '1', '1', '1', '2', '2', '2', '3', '3', '3', '3', '3')), "Order" = as.factor(c('general', 'pre', 'post', 'post', 'pre', 'post', 'general', 'post', 'pre', 'general', 'pre', 'post', 'pre', 'pre')), "Value" = as.numeric(c('1', '3','4','7','0','10', '1','0','12', '12', '3', '4', '6','8')))

Задача : Некоторые участники забыли / не смогли ответить на предварительный вопросник пре / пост пары, другие забыли / не удалось ответить на пост-анкету пре / пост-пары.

Цель : мне нужно добавить строку «pre» или «post» для каждой пары, которая не завершена. Следовательно, последовательные строки всегда должны читаться до публикации, до публикации и до публикации c. Добавленная строка должна включать идентификатор, а также значение из существующей части пары.

> df2
   ID    Order Value
1   1  general     1
2   1      pre     3
3   1     post     4
4   1      pre     7
5   1     post     7
6   1      pre     0
7   1     post    10
8   2  general     1
9   2      pre     0
10  2     post     0
11  2      pre    12
12  2     post    12
13  3  general    12
14  3      pre     3
15  3     post     4
16  3      pre     6
17  3     post     6
18  3      pre     8
19  3     post     8

См. Пример данных здесь:

df2 <- data.frame("ID" = as.factor(c('1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3')), "Order" = as.factor(c('general', 'pre', 'post', 'pre', 'post', 'pre', 'post', 'general', 'pre', 'post', 'pre', 'post', 'general', 'pre', 'post', 'pre', 'post', 'pre', 'post')), "Value" = as.numeric(c('1', '3', '4', '7', '7', '0', '10', '1', '0', '0', '12', '12', '12', '3', '4', '6', '6', '8', '8')))

Количество пре / пост-пар может быть разным для каждого участника.

Я задал похожий вопрос здесь - но это не сработало для данного конкретного случая. Другой предложенный решение также не сделал. Я пробовал разные версии функции complete () и expand.grid.

Ответы [ 3 ]

1 голос
/ 15 марта 2020

Это может быть альтернативный подход:

library(tidyverse)

df1 %>%
  mutate(rn = row_number()) %>%
  pivot_wider(id_cols = c(ID, rn), names_from = Order, values_from = Value) %>%
  mutate(post2 = if_else(!is.na(lead(post)), lead(post), pre),
         pre2 = if_else(!is.na(post2) & is.na(pre), post2, pre)) %>%
  select(-c(rn, pre, post)) %>%
  pivot_longer(cols = c(general, pre2, post2), names_to = "Order", values_to = "Value") %>%
  drop_na()

Вывод

# A tibble: 19 x 3
   ID    Order   Value
   <fct> <chr>   <dbl>
 1 1     general     1
 2 1     pre2        3
 3 1     post2       4
 4 1     pre2        7
 5 1     post2       7
 6 1     pre2        0
 7 1     post2      10
 8 2     general     1
 9 2     pre2        0
10 2     post2       0
11 2     pre2       12
12 2     post2      12
13 3     general    12
14 3     pre2        3
15 3     post2       4
16 3     pre2        6
17 3     post2       6
18 3     pre2        8
19 3     post2       8

Редактировать :

К Обобщите это решение для нескольких столбцов Value. Сначала вам потребуется pivot_longer, чтобы поместить данные в более работоспособный формат. Кроме того, вы захотите group_by переменную имени столбца, чтобы при использовании lead вы смотрели только значения, подходящие для этой переменной.

Скажем, например, у вас есть два столбца, Value1 и Value2:

df1 <- data.frame("ID" = as.factor(c('1', '1', '1', '1', '1', '1', '2', '2', '2', '3', '3', '3', '3', '3')), 
                  "Order" = as.factor(c('general', 'pre', 'post', 'post', 'pre', 'post', 'general', 'post', 'pre', 'general', 'pre', 'post', 'pre', 'pre')), 
                  "Value1" = as.numeric(c('1', '3','4','7','0','10', '1','0','12', '12', '3', '4', '6','8')),
                  "Value2" = as.numeric(c('4', '2','1','9','2','15', '2','11','18', '16', '5', '5', '8','10')))

Вы можете сделать следующее:

df1 %>%
  pivot_longer(cols = starts_with("Value"), names_to = "ValueName", values_to = "Value") %>%
  mutate(rn = row_number()) %>%
  pivot_wider(id_cols = c(ID, rn, ValueName), names_from = Order, values_from = Value) %>%
  group_by(ID, ValueName) %>%
  mutate(post2 = if_else(!is.na(lead(post)), lead(post), pre),
         pre2 = if_else(!is.na(post2) & is.na(pre), post2, pre)) %>%
  select(-c(rn, pre, post)) %>%
  rename(pre = pre2, post = post2) %>%
  pivot_longer(cols = c(general, pre, post), names_to = "Order", values_to = "Value") %>%
  drop_na() %>%
  arrange(ValueName, ID) %>%
  print(n=50)

Вывод

# A tibble: 38 x 4
# Groups:   ID, ValueName [6]
   ID    ValueName Order   Value
   <fct> <chr>     <chr>   <dbl>
 1 1     Value1    general     1
 2 1     Value1    pre         3
 3 1     Value1    post        4
 4 1     Value1    pre         7
 5 1     Value1    post        7
 6 1     Value1    pre         0
 7 1     Value1    post       10
 8 2     Value1    general     1
 9 2     Value1    pre         0
10 2     Value1    post        0
11 2     Value1    pre        12
12 2     Value1    post       12
13 3     Value1    general    12
14 3     Value1    pre         3
15 3     Value1    post        4
16 3     Value1    pre         6
17 3     Value1    post        6
18 3     Value1    pre         8
19 3     Value1    post        8
20 1     Value2    general     4
21 1     Value2    pre         2
22 1     Value2    post        1
23 1     Value2    pre         9
24 1     Value2    post        9
25 1     Value2    pre         2
26 1     Value2    post       15
27 2     Value2    general     2
28 2     Value2    pre        11
29 2     Value2    post       11
30 2     Value2    pre        18
31 2     Value2    post       18
32 3     Value2    general    16
33 3     Value2    pre         5
34 3     Value2    post        5
35 3     Value2    pre         8
36 3     Value2    post        8
37 3     Value2    pre        10
38 3     Value2    post       10

Данные оставлены в длинном формате - но может быть преобразован в широкий, в конце концов, с pivot_wider.

0 голосов
/ 16 марта 2020

Для полноты картины, здесь также есть решение data.table, в котором используются rowid(), CJ() и nafill(). В общем случае подход состоит из трех этапов:

  1. создание таблицы полных пар,
  2. объединение с исходной таблицей,
  3. заполнение пропущенных значений.
library(data.table)
setDT(df1)[, oid := rowid(ID, Order)][]
df1[, Order := factor(Order, level = c("general", "pre", "post"))]
tmp <- df1[, CJ(oid, Order, unique = TRUE), by = ID][!(oid > 1 & Order == "general")]
result <- df1[tmp, on = .(ID, Order, oid)][
  , Value := nafill(nafill(Value, "locf"), "nocb"), by = .(ID, oid)][, oid := NULL][]
result
    ID   Order Value
 1:  1 general     1
 2:  1     pre     3
 3:  1    post     4
 4:  1     pre     0
 5:  1    post     7
 6:  1     pre    10
 7:  1    post    10
 8:  2 general     1
 9:  2     pre    12
10:  2    post     0
11:  3 general    12
12:  3     pre     3
13:  3    post     4
14:  3     pre     6
15:  3    post     6
16:  3     pre     8
17:  3    post     8

Подробное объяснение

  1. После приведения df1 к классу data.table новый столбец oid добавлен счетчик строк, которые принадлежат ID и Order. Итак, df1 становится
    ID   Order Value oid
 1:  1 general     1   1
 2:  1     pre     3   1
 3:  1    post     4   1
 4:  1    post     7   2
 5:  1     pre     0   2
 6:  1    post    10   3
 7:  2 general     1   1
 8:  2    post     0   1
 9:  2     pre    12   1
10:  3 general    12   1
11:  3     pre     3   1
12:  3    post     4   1
13:  3     pre     6   2
14:  3     pre     8   3
Уровни факторов Order должны быть переупорядочены таким образом, чтобы «pre» был вторым уровнем, а «post» - третьим уровнем. Это необходимо для следующего шага Теперь создается таблица данных tmp, которая содержит все полные пары. Это достигается путем перекрестного соединения последовательности уникальных oid, например, 1, 2, 3 с уровнями факторов Order для каждого ID. CJ() похож на expand.grid(). Результат фильтруется, чтобы сохранить только одну «общую» строку и столько пар «pre» и «post», сколько требуется для каждого ID.
    ID oid   Order
 1:  1   1 general
 2:  1   1     pre
 3:  1   1    post
 4:  1   2     pre
 5:  1   2    post
 6:  1   3     pre
 7:  1   3    post
 8:  2   1 general
 9:  2   1     pre
10:  2   1    post
11:  3   1 general
12:  3   1     pre
13:  3   1    post
14:  3   2     pre
15:  3   2    post
16:  3   3     pre
17:  3   3    post
df1 - это правое соединение с tmp для добавления столбца Value к соответствующим строкам. Отсутствующие значения, в которых df1 не имеет соответствующей строки, отображаются как NA. Эти пропущенные значения заменяются последним наблюдением, переносимым вперед и следующим наблюдением, переносимым назад , т. Е. В обоих направлениях, с использованием функции nafill() (впервые для data.table версия 1.12.4 по состоянию на 03 октября 2019 года). Наконец, столбец oid удаляется.
0 голосов
/ 15 марта 2020

Это делает трюк:

df1 <- data.frame("ID" = as.factor(c('1', '1', '1', '1', '1', '1', '2', '2', '2', '3', '3', '3', '3', '3')), "Order" = as.factor(c('general', 'pre', 'post', 'post', 'pre', 'post', 'general', 'post', 'pre', 'general', 'pre', 'post', 'pre', 'pre')), "Value" = as.numeric(c('1', '3','4','7','0','10', '1','0','12', '12', '3', '4', '6','8')))
df2 <- data.frame("ID" = as.factor(c('1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3')), "Order" = as.factor(c('general', 'pre', 'post', 'pre', 'post', 'pre', 'post', 'general', 'pre', 'post', 'pre', 'post', 'general', 'pre', 'post', 'pre', 'post', 'pre', 'post')), "Value" = as.numeric(c('1', '3', '4', '7', '7', '0', '10', '1', '0', '0', '12', '12', '12', '3', '4', '6', '6', '8', '8')))

temp <- df1 %>% 
  mutate(
    ID = as.character(ID),
    Order = as.character(Order),
  ) %>% 
  group_by(ID) %>% 
  mutate(
    last = lag(Order),
    `next` = lead(Order),
    rowID = row_number(),
    filter = if_else((rowID == 2 & Order == "post") | (Order == "pre" & `next` != "post") | (Order == "post" & last != "pre"), 1, 0)
  ) %>% 
  ungroup() %>% 
  replace_na(list(filter = 1))
add_rows <- temp %>% 
  filter(filter == 1) %>% 
  mutate(
    Order = if_else(Order == "post", "pre", "post")
  )

temp %>% 
  bind_rows(add_rows) %>% 
  arrange(ID, rowID) %>% 
  select(ID, Order, Value) %>% 
  mutate(
    ID = as.factor(ID),
    Order = as.factor(Order),
  )
...