#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
вектор. Если вы этого не знаете, мы должны быть в состоянии рассчитать порядок, но пока я предполагаю, что это известно.
Подход основан на:
- сначала вычисляем
parent_level
также - вложите фрейм данных обоими
parent_level
иchild_level
- пользовательская функция, которую можно использовать с
purrr::accumulate2
илиpurrr::reduce2
, которая объединяет все данные.кадры в строке с использованиемleft_join
и в случае повторного объединения существующего столбца соответствующие столбцы объединяются в один
Перед применением этой пользовательской join_merge
функции:
- вложенные data.frames необходимо отсортировать в порядке убывания (
child_level
) - имена столбцов
parent
иchild
заменяются значениямиparent_level
иchild_level
- наконец
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. это не то, что мне нужно — иерархии между дочерним и родительским элементами теряются