Как свернуть столбцы фрейма данных по символу в середине имени столбца с различными именами столбцов?

#r #tidyverse

Вопрос:

Это похоже на вопрос, который я задавал ранее, но я упустил пару важных деталей: столбец идентификаторов и переменные XYZ.

У меня есть набор данных со следующей компоновкой (странные названия столбцов, я знаю):

 ID <- c(1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0)
XYZ1_a <- c(1, 2, 1, 2, 1, 2, 4, 2, 5, 1)
XYZ1_b <- c(2, 1, 1, 1, 2, 2, 4, 2, 1, 5)
ABC1a_1 <- c(1, 5, 3, 4, 3, 4, 5, 2, 2, 1)
ABC1b_1 <- c(4, 2, 1, 1, 5, 3, 2, 1, 1, 5)
ABC1a_2 <- c(4, 5, 5, 4, 2, 5, 5, 1, 2, 4)
ABC1b_2 <- c(2, 3, 3, 2, 2, 3, 2, 1, 4, 2)
ABC2a_1 <- c(2, 5, 3, 5, 3, 4, 5, 3, 2, 3)
ABC2b_1 <- c(1, 2, 2, 4, 5, 3, 2, 4, 1, 4)
ABC2a_2 <- c(2, 5, 5, 1, 2, 1, 5, 1, 3, 4)
ABC2b_2 <- c(2, 3, 3, 2, 1, 3, 1, 1, 2, 2)

df <- data.frame(ID, XYZ1_a, XYZ1_b, ABC1a_1, ABC1b_1, ABC1a_2, ABC1b_2, ABC2a_1, ABC2b_1, ABC2a_2, ABC2b_2)
 

Я хочу свернуть все ABC[N][x]_[n] переменные в одну ABC[N]_[n] переменную, как показано ниже, но мне также нужно сделать то же самое для столбцов с соглашением об XYZ именовании:

 ID <- c(1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0, 
        1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0)
XYZ1 <- c(1, 2, 1, 2, 1, 2, 4, 2, 5, 1, 2, 1, 1, 1, 2, 2, 4, 2, 1, 5)
ABC1_1 <- c(1, 5, 3, 4, 3, 4, 5, 2, 2, 1, 4, 2, 1, 1, 5, 3, 2, 1, 1, 5)
ABC1_2 <- c(4, 5, 5, 4, 2, 5, 5, 1, 2, 4, 2, 3, 3, 2, 2, 3, 2, 1, 4, 2)
ABC2_1 <- c(2, 5, 3, 5, 3, 4, 5, 3, 2, 3, 1, 2, 2, 4, 5, 3, 2, 4, 1, 4)
ABC2_2 <- c(2, 5, 5, 1, 2, 1, 5, 1, 3, 4, 2, 3, 3, 2, 1, 3, 1, 1, 2, 2)

df2 <- data.frame(ID, XYZ1, ABC1_1, ABC1_2, ABC2_1, ABC2_2)
 

Каков наилучший способ достичь этого, в идеале с tidyverse помощью решения?

Ответ №1:

Мы можем использовать перестановку подстрок в тех именах столбцов, которые starts_with «ABC», записав букву в группу, за которой следует подчеркивание, и одну или несколько цифр ( \d ) в качестве второй группы, при замене укажите обратную ссылку в обратном порядке при добавлении новой _ . В pivot_longer , укажите sep , чтобы соответствовать _ тому, что предшествует букве

 library(dplyr)
library(stringr)
library(tidyr)
df %>% 
   rename_with(~ str_replace(., "([a-z])_(\d )$", "_\2_\1"), 
      starts_with('AB')) %>%
   pivot_longer(cols = -ID, names_to = c(".value", "grp"), 
      names_sep = "_(?=[a-z])", values_drop_na = TRUE) %>%
   select(-grp)
 

-выход

 # A tibble: 20 x 6
      ID  XYZ1 ABC1_1 ABC1_2 ABC2_1 ABC2_2
   <dbl> <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
 1   1.1     1      1      4      2      2
 2   1.1     2      4      2      1      2
 3   1.2     2      5      5      5      5
 4   1.2     1      2      3      2      3
 5   1.3     1      3      5      3      5
 6   1.3     1      1      3      2      3
 7   1.4     2      4      4      5      1
 8   1.4     1      1      2      4      2
 9   1.5     1      3      2      3      2
10   1.5     2      5      2      5      1
11   1.6     2      4      5      4      1
12   1.6     2      3      3      3      3
13   1.7     4      5      5      5      5
14   1.7     4      2      2      2      1
15   1.8     2      2      1      3      1
16   1.8     2      1      1      4      1
17   1.9     5      2      2      2      3
18   1.9     1      1      4      1      2
19   2       1      1      4      3      4
20   2       5      5      2      4      2
 

В более старой версии используйте rename_at

 df %>% 
   rename_at(vars(starts_with('AB')), 
        ~ str_replace(., "([a-z])_(\d )$", "_\2_\1")) %>%
   pivot_longer(cols = -ID, names_to = c(".value", "grp"), 
      names_sep = "_(?=[a-z])", values_drop_na = TRUE) %>%
   select(-grp)
 

-выход

 # A tibble: 20 x 6
      ID  XYZ1 ABC1_1 ABC1_2 ABC2_1 ABC2_2
   <dbl> <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
 1   1.1     1      1      4      2      2
 2   1.1     2      4      2      1      2
 3   1.2     2      5      5      5      5
 4   1.2     1      2      3      2      3
 5   1.3     1      3      5      3      5
 6   1.3     1      1      3      2      3
 7   1.4     2      4      4      5      1
 8   1.4     1      1      2      4      2
 9   1.5     1      3      2      3      2
10   1.5     2      5      2      5      1
11   1.6     2      4      5      4      1
12   1.6     2      3      3      3      3
13   1.7     4      5      5      5      5
14   1.7     4      2      2      2      1
15   1.8     2      2      1      3      1
16   1.8     2      1      1      4      1
17   1.9     5      2      2      2      3
18   1.9     1      1      4      1      2
19   2       1      1      4      3      4
20   2       5      5      2      4      2
 

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

1. Это здорово. Есть ли альтернатива, которая не использует rename_with? Я ограничен более старой версией dplyr.

2. Я получаю сообщение об ошибке, что функция starts_with() должна использоваться в функции выбора .

3. @R. Баратеон, с которым я vars работал, и, похоже, он работает с моей текущей версией. Возможно, в более старых версиях есть какая-то ошибка

4. Не волнуйтесь, я позвонил в dplyr::vars, и это все решило. Спасибо.

5. @R. Баратеон, вероятно, какая-то маскировка функций