r условный широкий на длинный с шаблоном имени столбца

#r #reshape #tidyr #melt #data-transform

#r #изменить #tidyr #расплавить #преобразование данных

Вопрос:

Это немного сложный набор данных, в котором столбцы расположены следующим образом.

 ID   C.Date      T.Date      C(Area)   T(Area)    Level(closet)_1   Venti_1    Level(closet)_2   Venti_2
733  2013.06.18  2013.06.18  65.2      42.1       C6                0          C3                1
537  2015.10.01  2015.15.01  34.5      27.2       C3                0          T11               0
909  2016-01-14  2016-01-14  15.1      25.9       T4                1          T2                1
 

Правило

 Step1 :  Consider columns: ID, C.Date, C(Area), Level(closet)_1, Venti_1, Level(closet)_2, Venti_2
         Rearrange the data like this.

         ID  Index   Date       Ref.Level   Area    Level(closet)    Venti
         733 1       2013.06.18 C           65.2    C6                0 
         733 2       2013.06.18 C           65.2    C3                1 

Step2 :  Consider columns: ID, T.Date, T(Area), Level(closet)_1, Venti_1, Level(closet)_2, Venti_2
         Rearrange the data like this.

         ID  Index   Date       Ref.Level   Area    Level(closet)    Venti
         733 3       2013.06.18 T           42.1    NA                NA  
 

Обратите внимание, что как Step1, так и Step2 ссылаются на значения в столбцах Level(closet)_1, Venti_1, Level(closet)_2, Venti_2 . Разница заключается в шаге 2, когда есть значения для T.Date и T(Area) ожидается, что любое из Level(closet) значений будет начинаться с T* , в 1-м идентификаторе 733 их НЕ было. Таким образом, преобразованная 3-я строка набора данных имеет значения NA для столбцов Level(closet), Venti . 2-й идентификатор 537 снова имеет оба T.Date T(Area) значения и, опять же, на основе шага 2 мы ищем Level(closet) значения столбцов, которые начинаются с T* в этом случае Level(closet)_2 содержит значение T11 , поэтому для преобразованных в ширину данных для идентификатора 523 будет

Шаг 1: рассмотрим столбцы: ID, C.Дата, C (область), Уровень (шкаф) _1, Venti_1, Уровень (шкаф) _2, Venti_2 Переставьте данные следующим образом.

      ID  Index    Date       Ref.Level   Area    Level(closet)    Venti
     537  1       2015.10.01 C           34.5    C3                0 
     
 

Шаг 2: рассмотрим столбцы: Идентификатор, T.Дата, T (область), Уровень (шкаф) _1, Венти_1, Уровень (шкаф) _2, Венти_2
Измените порядок данных следующим образом.

      ID  Index   Date       Ref.Level   Area    Level(closet)    Venti
     537  2      2015.15.01 T           27.2    T11                0 
 

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

      ID   Index   Date       Ref.Level   Area    Level(closet)    Venti
     733  1       2013.06.18 C           65.2    C6                0 
     733  2       2013.06.18 C           65.2    C3                1 
     733  3       2013.06.18 T           42.1    NA                NA 
     537  1       2015.10.01 C           34.5    C3                0 
     537  2       2015.15.01 T           27.2    T11               0 
     909  1       2016-01-14 C           15.1    NA                NA
     909  2       2016-01-14 T           25.9    T4                1
     909  3       2016-01-14 T           25.9    T2                1
 

Извините, это немного сложно. На поверхностном уровне это выглядит как взять несколько строк в широком формате и преобразовать их в длинный формат, но есть вложенный ifelse, чтобы увидеть, есть ли какие-либо значения, начинающиеся с T* в Level(closet) столбцах. Я совершенно не понимаю, как структурировать это в таком длинном формате, как этот. Любая помощь или предложения сильно сокращены. Спасибо.


библиотека (tidyverse)

 df <- tibble::tribble(~`ID`, ~`C.Date`, ~`T.Date`, ~`C(Area)`, ~`T(Area)`, ~`Level(closet)_1`, ~`Venti_1`, ~`Level(closet)_2`, ~`Venti_2`,
                "733", "2013.06.18", "2013.06.18", "65.2", "42.1", "C6", "0", "C3", "1",
                "537", "2015.10.01", "2015.15.01", "34.5", "27.2", "C3", "0", "T11", "0",
                "909", "2016-01-14", "2016-01-14", "15.1", "25.9", "T4", "1", "T2", "1"
                )
 

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

1. Можете ли вы предоставить небольшой воспроизводимый пример ваших данных?

2. @deschen, обновил вопрос с данными

Ответ №1:

Следующий код работает, вы можете легко следовать ему, но, вероятно, это не самый эффективный способ сделать это, но он выполняет свою работу.

 library(tidyverse)

tibble::tribble(~`ID`, ~`C.Date`, ~`T.Date`, ~`C(Area)`, ~`T(Area)`, ~`Level(closet)_1`, ~`Venti_1`, ~`Level(closet)_2`, ~`Venti_2`,
                "733", "2013.06.18", "2013.06.18", "65.2", "42.1", "C6", "0", "C3", "1",
                "537", "2015.10.01", "2015.15.01", "34.5", "27.2", "C3", "0", "T11", "0",
                "909", "2016-01-14", "2016-01-14", "15.1", "25.9", "T4", "1", "T2", "1"
                ) -> df
df
#> # A tibble: 3 x 9
#>   ID    C.Date T.Date `C(Area)` `T(Area)` `Level(closet)_… Venti_1
#>   <chr> <chr>  <chr>  <chr>     <chr>     <chr>            <chr>  
#> 1 733   2013.… 2013.… 65.2      42.1      C6               0      
#> 2 537   2015.… 2015.… 34.5      27.2      C3               0      
#> 3 909   2016-… 2016-… 15.1      25.9      T4               1      
#> # … with 2 more variables: `Level(closet)_2` <chr>, Venti_2 <chr>

df %>% 
  mutate(across(c(1,4,5,7,9), as.numeric)) %>% 
  janitor::clean_names()-> df1

df1
#> # A tibble: 3 x 9
#>      id c_date t_date c_area t_area level_closet_1 venti_1 level_closet_2
#>   <dbl> <chr>  <chr>   <dbl>  <dbl> <chr>            <dbl> <chr>         
#> 1   733 2013.… 2013.…   65.2   42.1 C6                   0 C3            
#> 2   537 2015.… 2015.…   34.5   27.2 C3                   0 T11           
#> 3   909 2016-… 2016-…   15.1   25.9 T4                   1 T2            
#> # … with 1 more variable: venti_2 <dbl>
  
df1 %>% 
  select(id, c_date, c_area) -> df2

df2
#> # A tibble: 3 x 3
#>      id c_date     c_area
#>   <dbl> <chr>       <dbl>
#> 1   733 2013.06.18   65.2
#> 2   537 2015.10.01   34.5
#> 3   909 2016-01-14   15.1

df1 %>% 
  select(id, t_date, t_area) -> df3

df3
#> # A tibble: 3 x 3
#>      id t_date     t_area
#>   <dbl> <chr>       <dbl>
#> 1   733 2013.06.18   42.1
#> 2   537 2015.15.01   27.2
#> 3   909 2016-01-14   25.9

df1 %>% 
  select(id, level_closet_1, level_closet_2) %>% 
  pivot_longer(-1) %>% 
  left_join(df2) %>% 
  filter(str_detect(value, "C")) %>% 
  rename(date = c_date,
         area = c_area)-> c_df
#> Joining, by = "id"

c_df
#> # A tibble: 3 x 5
#>      id name           value date        area
#>   <dbl> <chr>          <chr> <chr>      <dbl>
#> 1   733 level_closet_1 C6    2013.06.18  65.2
#> 2   733 level_closet_2 C3    2013.06.18  65.2
#> 3   537 level_closet_1 C3    2015.10.01  34.5

df1 %>% 
  select(id, level_closet_1, level_closet_2) %>% 
  pivot_longer(-1) %>% 
  left_join(df3) %>% 
  filter(str_detect(value, "T")) %>% 
  rename(date = t_date,
         area = t_area) -> t_df
#> Joining, by = "id"

t_df
#> # A tibble: 3 x 5
#>      id name           value date        area
#>   <dbl> <chr>          <chr> <chr>      <dbl>
#> 1   537 level_closet_2 T11   2015.15.01  27.2
#> 2   909 level_closet_1 T4    2016-01-14  25.9
#> 3   909 level_closet_2 T2    2016-01-14  25.9

c_df %>% 
  bind_rows(t_df) -> ct_df

ct_df
#> # A tibble: 6 x 5
#>      id name           value date        area
#>   <dbl> <chr>          <chr> <chr>      <dbl>
#> 1   733 level_closet_1 C6    2013.06.18  65.2
#> 2   733 level_closet_2 C3    2013.06.18  65.2
#> 3   537 level_closet_1 C3    2015.10.01  34.5
#> 4   537 level_closet_2 T11   2015.15.01  27.2
#> 5   909 level_closet_1 T4    2016-01-14  25.9
#> 6   909 level_closet_2 T2    2016-01-14  25.9

df1 %>% 
  select(id, level_closet_1, venti_1) %>% 
  bind_rows(df1 %>% 
              select(id, level_closet_2, venti_2)) -> df_venti

t(apply(df_venti, 1, function(x) c(x[!is.na(x)], x[is.na(x)]))) -> df_venti[] 

df_venti
#> # A tibble: 6 x 5
#>   id    level_closet_1 venti_1 level_closet_2 venti_2
#>   <chr> <chr>          <chr>   <chr>          <chr>  
#> 1 733   C6             " 0"    <NA>           <NA>   
#> 2 537   C3             " 0"    <NA>           <NA>   
#> 3 909   T4             " 1"    <NA>           <NA>   
#> 4 733   C3             " 1"    <NA>           <NA>   
#> 5 537   T11            " 0"    <NA>           <NA>   
#> 6 909   T2             " 1"    <NA>           <NA>

df_venti %>% 
  select(1:3) %>% 
  rename(value = level_closet_1,
         venti = venti_1) %>% 
  mutate(venti = venti %>% as.numeric(),
         id = id %>% as.numeric()) -> venti_df2

venti_df2
#> # A tibble: 6 x 3
#>      id value venti
#>   <dbl> <chr> <dbl>
#> 1   733 C6        0
#> 2   537 C3        0
#> 3   909 T4        1
#> 4   733 C3        1
#> 5   537 T11       0
#> 6   909 T2        1

ct_df %>% 
  left_join(venti_df2) -> df_with_venti
#> Joining, by = c("id", "value")

df_with_venti
#> # A tibble: 6 x 6
#>      id name           value date        area venti
#>   <dbl> <chr>          <chr> <chr>      <dbl> <dbl>
#> 1   733 level_closet_1 C6    2013.06.18  65.2     0
#> 2   733 level_closet_2 C3    2013.06.18  65.2     1
#> 3   537 level_closet_1 C3    2015.10.01  34.5     0
#> 4   537 level_closet_2 T11   2015.15.01  27.2     0
#> 5   909 level_closet_1 T4    2016-01-14  25.9     1
#> 6   909 level_closet_2 T2    2016-01-14  25.9     1


df_with_venti %>%
  mutate(value = value %>% str_remove_all('[0-9] ')) %>% 
  mutate(mm = 1) %>% 
  complete(id, value, fill = list(mm = 0)) %>% 
  group_by(id, value) %>% 
  summarise(count = sum(mm)) %>% 
  filter(count == 0) -> missing_df
#> `summarise()` regrouping output by 'id' (override with `.groups` argument)

missing_df
#> # A tibble: 2 x 3
#> # Groups:   id [2]
#>      id value count
#>   <dbl> <chr> <dbl>
#> 1   733 T         0
#> 2   909 C         0

missing_df %>% 
  filter(value == "C") %>% 
  pull(id) -> c_missing

c_missing
#> [1] 909

missing_df %>% 
  filter(value == "T") %>% 
  pull(id) -> t_missing 

t_missing
#> [1] 733

df1 %>% 
  filter(id %in% c_missing) %>% 
  select(id, c_date, c_area) %>% 
  rename(date = c_date,
         area = c_area) %>% 
  mutate(ref_level = "C",
         value = NA,
         venti = NA) -> c_fill_df

c_fill_df
#> # A tibble: 1 x 6
#>      id date        area ref_level value venti
#>   <dbl> <chr>      <dbl> <chr>     <lgl> <lgl>
#> 1   909 2016-01-14  15.1 C         NA    NA

df1 %>% 
  filter(id %in% t_missing) %>% 
  select(id, t_date, t_area) %>% 
  rename(date = t_date,
         area = t_area) %>% 
  mutate(ref_level = "T",
         value = NA,
         venti = NA) -> t_fill_df

t_fill_df
#> # A tibble: 1 x 6
#>      id date        area ref_level value venti
#>   <dbl> <chr>      <dbl> <chr>     <lgl> <lgl>
#> 1   733 2013.06.18  42.1 T         NA    NA

df_with_venti %>% 
  select(id, date, area, value, venti) %>% 
  mutate(ref_level = value %>% str_remove_all('[0-9] ')) %>% 
  bind_rows(c_fill_df) %>% 
  bind_rows(t_fill_df) %>% 
  group_by(id) %>% 
  mutate(index = row_number()) %>% 
  arrange(id) %>% 
  select(id, index, date, ref_level, area, value, venti) %>% 
  rename(level_closet = value)
#> # A tibble: 8 x 7
#> # Groups:   id [3]
#>      id index date       ref_level  area level_closet venti
#>   <dbl> <int> <chr>      <chr>     <dbl> <chr>        <dbl>
#> 1   537     1 2015.10.01 C          34.5 C3               0
#> 2   537     2 2015.15.01 T          27.2 T11              0
#> 3   733     1 2013.06.18 C          65.2 C6               0
#> 4   733     2 2013.06.18 C          65.2 C3               1
#> 5   733     3 2013.06.18 T          42.1 <NA>            NA
#> 6   909     1 2016-01-14 T          25.9 T4               1
#> 7   909     2 2016-01-14 T          25.9 T2               1
#> 8   909     3 2016-01-14 C          15.1 <NA>            NA
 

Создано 2021-01-22 пакетом reprex (версия 0.3.0)

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

1. @MohanGovindasmy , left joins в ваших решениях создается больше строк, чем ожидалось. В основном из-за ситуации «один ко многим». Это создает некоторые проблемы. Как мы предполагаем исправить эту часть. Спасибо.

2. @Science11 left_join не должен создавать больше строк, он сохранит все строки в исходном фрейме данных и вернет NA , если ключ отсутствует во втором фрейме данных. Я не могу понять, какая часть кода вызывает проблему, о которой вы упомянули. Не могли бы вы указать на это мне?

3. вы создаете df_venti , и это не включает столбец даты. Затем это изменится на df_venti2 . Затем вы объединяете это df_venti2 с ct_df . В обоих df_venti2 и ct_df в обоих отсутствуют столбцы даты. If left_join выполняется только на основе id игнорирования date . Мы не знаем, является ли это слияние точным. Это не просто столбец id , date столбец тоже важен. Нам нужно убедиться, что мы фиксируем измерения для этого id , date а не просто на основе слепого слияния id . Имеет ли это смысл?

4. Я сильно позаимствовал ваши идеи и несколько раз модифицировал код, и это сработало. Еще раз спасибо.

5. @Science11 рад, что могу помочь. Приносим извинения за то, что не решили последнюю проблему, поскольку ее было трудно понять с приведенными образцами данных

Ответ №2:

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

 df %>% pivot_longer(cols=c(C.Date, T.Date, `C(Area)`, `T(Area)`)) %>%
  separate(col="name", into=c("Ref.Level", "name"), sep="(\.)|(\()") %>%
  mutate(name=str_replace(name, "\)", "")) %>% pivot_wider() %>%
  pivot_longer(cols=c(`Level(closet)_1`, `Level(closet)_2`, Venti_1, Venti_2)) %>%
  separate(col="name", into=c("name", "index"), sep="_") %>% pivot_wider() %>% select(-index) %>%
  nest(data=c(`Level(closet)`, Venti)) %>% mutate(data=map2(data, Ref.Level, function(data, ref_level){
    data <- data %>% filter(str_detect(`Level(closet)`, ref_level))
    if(nrow(data)==0) data <- tibble(`Level(closet)`=NA_character_, Venti=NA_character_)
    return(data)
  })) %>% unnest(cols=data) %>% group_by(ID) %>% mutate(Index=row_number(), .after=ID) %>% ungroup(ID)
 

Хитрость заключается в том, чтобы сначала изменить фрейм данных в очень длинный формат и вложить Level(closet), Venti столбцы для фильтрации строк.

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

1. этот pivot_wider шаг вызвал у меня некоторую головную боль. Данные содержали некоторые вложенные объекты. Во многом это связано с чтением некоторых строк с задержкой. Позже я понял, что добавление строки для включения row.numbers решит эту проблему. Спасибо за предложение.