длинный формат родительского дочернего фрейма данных в широкий формат с полной иерархией

#python #r #dataframe #reshape

#python #r #фрейм данных #изменить форму

Вопрос:

У меня есть следующий фрейм данных с длинным форматом с двумя столбцами, содержащими вложенную родительско-дочернюю иерархию:

 parent,child,child_level
d     ,sf   ,x
d     ,st   ,x
d     ,s0   ,x
sf    ,gr4  ,l
sf    ,gr3  ,l
st    ,grd  ,l
st    ,gr9  ,l
s0    ,n7   ,l
s0    ,b12  ,l
grd   ,nyvc ,b
gr3   ,trub2,b
b12   ,ngb2 ,b
b12   ,ggb8 ,b
nyvc  ,xtr2d,i
trub2 ,xtuD ,i
gr4   ,stab3,i
gr9   ,ubc8 ,i
n7    ,ubc2 ,i
ggb8  ,drik2,i
 

Моя цель — вывести родительские и дочерние столбцы в широкий формат. Имена столбцов должны соответствовать соответствующему уровню в столбце child_level:

  ,x ,l  ,b    ,i
d,sf,gr4,NA   ,stab3
d,sf,gr3,trub2,xtuD
d,st,grd,nyvc ,xtr2d
d,st,gr9,NA   ,ubc8
d,s0,n7 ,NA   ,ubc2
d,s0,b12,ngb2 ,NA
d,s0,b12,ggb8 ,drik2
 

Нет дополнительной информации, обозначающей порядок или ранг уровня, которая возникает только из дочерних и родительских столбцов. Кроме того, не все строки выходной таблицы будут содержать все уровни иерархии — они должны быть заполнены NA .

EDIT1 (для пояснения): решение на языке R или Python будет работать, поэтому я представляю общую таблицу ввода (для чтения, например, в формате csv). Кроме того, приведенная ниже таблица составлена вручную — я на самом деле не знаю, как туда добраться программно.

EDIT2: строки не упорядочены, т. Е. Дочерний уровень может быть в любом порядке, поэтому это должен быть какой-то рекурсивный подход.

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

1. Я не понимаю, как вы на самом деле переходите от входной таблицы к выходной? Что такое столбец ‘root’? Не могли бы вы на самом деле опубликовать полный набор входных данных и какой результат вы ожидаете?

2. столбец «root» — это просто самый низкий возможный уровень. результат, который я хотел бы получить, — это таблица ниже. полный набор данных очень длинный и не упорядоченный, поэтому он бесполезен при описании структуры данных

3. Опять же, это на самом деле не объясняет, как определить «минимально возможный уровень». В вашем примере столбец «root», похоже, заполнен одним значением из столбца «parent», остальные из которых удалены? Нет необходимости публиковать полные данные, но показывать более короткий, но полный ввод данных и желаемый результат. Кроме того, пожалуйста, используйте dput(), чтобы данные можно было легко вставить в скрипт — вместо того, чтобы всем приходилось копировать данные, вставлять их в таблицу, сохранять как файл данных, а затем импортировать в скрипт.

4. Я думал, что это то, что я сделал? верхняя таблица представляет собой более короткую версию исходных данных, а нижняя таблица — желаемый результат. кроме того, это не вопрос, специфичный для R-python тоже подходит, поэтому я чувствовал, что структура dput будет ограничивать людей.

Ответ №1:

Обновленный ответ

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

Подход основан на:

  1. сначала вычисляем parent_level также
  2. вложите фрейм данных обоими parent_level и child_level
  3. пользовательская функция, которую можно использовать с purrr::accumulate2 или purrr::reduce2 , которая объединяет все данные.кадры в строке с использованием left_join и в случае повторного объединения существующего столбца соответствующие столбцы объединяются в один

Перед применением этой пользовательской join_merge функции:

  1. вложенные data.frames необходимо отсортировать в порядке убывания ( child_level )
  2. имена столбцов parent и child заменяются значениями parent_level и child_level
  3. наконец parent_level , и child_level объединяются в вызываемый вектор arg_ls , который передается в качестве .y аргумента accumulate2 (или альтернативно reduce2 )

Я надеюсь, что это работает с вашими реальными данными.

 library(tidyverse)

dat <- tribble(
  ~ parent, ~child, ~child_level,
  "d"     ,"sf"   ,"x",
  "d"     ,"st"   ,"x",
  "d"     ,"s0"   ,"x",
  "sf"    ,"gr4"  ,"l",
  "sf"    ,"gr3"  ,"l",
  "st"    ,"grd"  ,"l",
  "st"    ,"gr9"  ,"l",
  "s0"    ,"n7"   ,"l",
  "s0"    ,"b12"  ,"l",
  "grd"   ,"nyvc" ,"b",
  "gr3"   ,"trub2","b",
  "b12"   ,"ngb2" ,"b",
  "b12"   ,"ggb8" ,"b",
  "nyvc"  ,"xtr2d","i",
  "trub2" ,"xtuD" ,"i",
  "gr4"   ,"stab3","i",
  "gr9"   ,"ubc8" ,"i",
  "n7"    ,"ubc2" ,"i",
  "ggb8"  ,"drik2","i"
)
# in a first step we calculate the `parent_level`
dat <- dat %>% 
  left_join(., select(., -parent), by = c("parent" = "child")) %>% 
  rename("child_level" = "child_level.x",
         "parent_level" = "child_level.y") %>% 
  mutate(parent_level = replace_na(parent_level, "o"))

# we need this function to work with accumulate2 or reduce2
join_merge <- function(df1, df2, .rename) {
  res <- left_join(df1, df2, by = .rename[1]) 

  # in case an existing column is joined again, we need to merge it together
  if(length(colnames(select(res, starts_with(all_of(.rename[2]))))) > 1) {
    res <- mutate(res, across(matches(paste0(.rename[2], ".x")), 
                              ~ if_else(is.na(.x), eval(sym(paste0(.rename[2], ".y"))), .x))) %>% 
      select(-all_of(paste0(.rename[2], ".y"))) %>% 
      rename(!! .rename[2] := paste0(.rename[2], ".x"))
  }
  res
}


# accumulate is used to show how the final result is buildt
dat %>% 
  nest_by(child_level, parent_level) %>% 
  arrange(child_level == "i", desc(child_level)) %>% 
  mutate(arg_ls = list(c(parent_level, child_level))) %>% 
  mutate(data = list(rename_with(data,
                                 ~ paste0(child_level),
                                 "child") %>%
           rename_with(~ paste0(parent_level),
                       "parent"))) %>%
  ungroup %>% 
  mutate(dat_acc = accumulate2(data,
                              arg_ls[-1],
                              join_merge)) %>% 
  pull(dat_acc)
#> [[1]]
#> # A tibble: 3 x 2
#>   o     x    
#>   <chr> <chr>
#> 1 d     sf   
#> 2 d     st   
#> 3 d     s0   
#> 
#> [[2]]
#> # A tibble: 6 x 3
#>   o     x     l    
#>   <chr> <chr> <chr>
#> 1 d     sf    gr4  
#> 2 d     sf    gr3  
#> 3 d     st    grd  
#> 4 d     st    gr9  
#> 5 d     s0    n7   
#> 6 d     s0    b12  
#> 
#> [[3]]
#> # A tibble: 7 x 4
#>   o     x     l     b    
#>   <chr> <chr> <chr> <chr>
#> 1 d     sf    gr4   <NA> 
#> 2 d     sf    gr3   trub2
#> 3 d     st    grd   nyvc 
#> 4 d     st    gr9   <NA> 
#> 5 d     s0    n7    <NA> 
#> 6 d     s0    b12   ngb2 
#> 7 d     s0    b12   ggb8 
#> 
#> [[4]]
#> # A tibble: 7 x 5
#>   o     x     l     b     i    
#>   <chr> <chr> <chr> <chr> <chr>
#> 1 d     sf    gr4   <NA>  <NA> 
#> 2 d     sf    gr3   trub2 xtuD 
#> 3 d     st    grd   nyvc  xtr2d
#> 4 d     st    gr9   <NA>  <NA> 
#> 5 d     s0    n7    <NA>  <NA> 
#> 6 d     s0    b12   ngb2  <NA> 
#> 7 d     s0    b12   ggb8  drik2
#> 
#> [[5]]
#> # A tibble: 7 x 5
#>   o     x     l     b     i    
#>   <chr> <chr> <chr> <chr> <chr>
#> 1 d     sf    gr4   <NA>  stab3
#> 2 d     sf    gr3   trub2 xtuD 
#> 3 d     st    grd   nyvc  xtr2d
#> 4 d     st    gr9   <NA>  ubc8 
#> 5 d     s0    n7    <NA>  ubc2 
#> 6 d     s0    b12   ngb2  <NA> 
#> 7 d     s0    b12   ggb8  drik2
 

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

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

1. приятно! однако в реальных данных я получаю сообщение об ошибке: «Ошибка: проблема с mutate() вводом data . Имена x должны быть уникальными. x Эти имена дублируются:» Я думаю, это может быть потому, что у меня иногда есть дочерние элементы, у которых родители находятся на одном и том же дочернем уровне, чего на самом деле не должно быть. Сначала мне нужно выяснить, что там происходит

Ответ №2:

Без дополнительной информации вот как я обычно подхожу к проблеме. Я показываю tidyverse решение, но, конечно, это можно сделать и в base R.

 data <- structure(list(child = structure(c(10L, 11L, 9L, 4L, 5L, 5L, 
6L, 1L, 8L, 13L, 7L, 3L, 16L, 17L, 12L, 15L, 14L, 2L), .Label = c("b12", 
"drik2", "ggb8", "gr4", "grd", "n7", "ngb2", "nyvc", "s0", "sf", 
"st", "stab3", "trub2", "ubc2", "ubc8", "xtr2d", "xtuD"), class = "factor"), 
    parent = structure(c(2L, 2L, 2L, 11L, 11L, 12L, 10L, 10L, 
    7L, 4L, 1L, 1L, 9L, 13L, 5L, 6L, 8L, 3L), .Label = c("b12", 
    "d", "ggb8", "gr3", "gr4", "gr9", "grd", "n7", "nyvc", "s0", 
    "sf", "st", "trub2"), class = "factor"), child_level = structure(c(4L, 
    4L, 4L, 3L, 3L, 3L, 3L, 3L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 
    2L, 2L), .Label = c("b", "i", "t", "x"), class = "factor")), class = "data.frame", row.names = c(NA, 
-18L))

library(tidyverse)

pivot <- data %>% mutate(unique = rownames(data)) %>% pivot_wider(id_cols = unique, names_from = child_level, values_from = child) %>% select(!unique)
 

Ввод выглядит следующим образом:

 # > data
#    child parent child_level
# 1     sf      d           x
# 2     st      d           x
# 3     s0      d           x
# 4    gr4     sf           t
# 5    grd     sf           t
# 6    grd     st           t
# 7     n7     s0           t
# 8    b12     s0           t
# 9   nyvc    grd           b
# 10 trub2    gr3           b
# 11  ngb2    b12           b
# 12  ggb8    b12           b
# 13 xtr2d   nyvc           i
# 14  xtuD  trub2           i
# 15 stab3    gr4           i
# 16  ubc8    gr9           i
# 17  ubc2     n7           i
# 18 drik2   ggb8           i
 

И он выведет это:

 # > pivot
# # A tibble: 18 x 4
#    x     t     b     i    
#    <fct> <fct> <fct> <fct>
#  1 sf    NA    NA    NA   
#  2 st    NA    NA    NA   
#  3 s0    NA    NA    NA   
#  4 NA    gr4   NA    NA   
#  5 NA    grd   NA    NA   
#  6 NA    grd   NA    NA   
#  7 NA    n7    NA    NA   
#  8 NA    b12   NA    NA   
#  9 NA    NA    nyvc  NA   
# 10 NA    NA    trub2 NA   
# 11 NA    NA    ngb2  NA   
# 12 NA    NA    ggb8  NA   
# 13 NA    NA    NA    xtr2d
# 14 NA    NA    NA    xtuD 
# 15 NA    NA    NA    stab3
# 16 NA    NA    NA    ubc8 
# 17 NA    NA    NA    ubc2 
# 18 NA    NA    NA    drik2
 

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

1. это не то, что мне нужно — иерархии между дочерним и родительским элементами теряются