在 R 数据框中解包含有列表的数据

3

我有一个dataframe,其中一个字段包含长度不同的列表。我想将该字段中列表的每个元素提取到自己的字段中,以便我可以将结果收集到一个长dataframe中,每个id对应一个列表元素。

以下是一个示例dataframe

dat <- structure(list(id = c("509935", "727889", "864607", "1234243", 
        "1020959", "221975"), some_date = c("2/09/1967", "28/04/1976", 
        "22/12/2017", "7/02/2006", "10/03/2019", "21/10/1935"), df_list = list(
            "018084131", c("062197171", "062171593"), c("064601923", 
            "068994009", "069831651"), c("071141584", "073129537"), c("061498574", 
            "065859718", "067251995", "069447806"), "064623976")), class = c("tbl_df", 
        "tbl", "data.frame"), row.names = c(NA, -6L))

我已经写了一些代码来实现我想要的最终结果,但是我没有以DRY的方式完成。以下是我尝试过的内容。

res_n 是以下函数:

res_n <- function(field, n) {
    field[n]
}

dat <- dat %>% mutate(res1 = map(df_list, res_n, 1))
dat <- dat %>% mutate(res2 = map(df_list, res_n, 2))
dat <- dat %>% mutate(res3 = map(df_list, res_n, 3))

这将返回一个数据框,其中df_list中的三个列表元素将分别位于它们自己的列中。

有了这个,我可以实现我想要的,并生成最终的dataframe结果,如下所示:

dat_final <- gather(dat, test, labno, -df_list, -some_date, -id) %>% 
    select(-df_list) %>% 
    mutate(labno = as.integer(labno)) %>% 
    filter(!is.na(labno))

为避免DRY方法,我使用了for循环来尝试消除重复的代码。然而,我现在还无法以所需的方式使其正常工作,以达到最终目标。以下是我尝试的for循环代码。
 for (i in 3) {
     dat %>% mutate(paste(res, i, sep = '_') = map(results, res_n, i)) }

有人能帮我优化代码,以消除生成结果的重复行吗?请。

1
@akrun - 抱歉,这是一个用于为映射中的每个新列命名的函数。现在已添加。 - John
3个回答

5

我们可以使用unnest_wider来避免重复使用map

library(dplyr)
library(tidyr)
library(stringr)
out <- dat %>%
         unnest_wider(df_list, names_repair = ~ 
                     str_remove(str_c("res", .x), "[.]+"))
out
# A tibble: 6 x 6
#  id      some_date  res1      res2      res3      res4     
#  <chr>   <chr>      <chr>     <chr>     <chr>     <chr>    
#1 509935  2/09/1967  018084131 <NA>      <NA>      <NA>     
#2 727889  28/04/1976 062197171 062171593 <NA>      <NA>     
#3 864607  22/12/2017 064601923 068994009 069831651 <NA>     
#4 1234243 7/02/2006  071141584 073129537 <NA>      <NA>     
#5 1020959 10/03/2019 061498574 065859718 067251995 069447806
#6 221975  21/10/1935 064623976 <NA>      <NA>      <NA>     

编辑:基于 @Phil 的评论

现在,使用 pivot_longer 将其转换为“长格式”。

out %>% 
    pivot_longer(cols = starts_with('res'), values_drop_na = TRUE) %>%
    mutate(value = as.integer(value))
# A tibble: 13 x 4
#   id      some_date  name     value
#   <chr>   <chr>      <chr>    <int>
# 1 509935  2/09/1967  res1  18084131
# 2 727889  28/04/1976 res1  62197171
# 3 727889  28/04/1976 res2  62171593
# 4 864607  22/12/2017 res1  64601923
# 5 864607  22/12/2017 res2  68994009
# 6 864607  22/12/2017 res3  69831651
# 7 1234243 7/02/2006  res1  71141584
# 8 1234243 7/02/2006  res2  73129537
# 9 1020959 10/03/2019 res1  61498574
#10 1020959 10/03/2019 res2  65859718
#11 1020959 10/03/2019 res3  67251995
#12 1020959 10/03/2019 res4  69447806
#13 221975  21/10/1935 res1  64623976

注意:如果我们勾选?unnest,它会说生命周期已弃用。

nest(.data, ..., .key = deprecated())

unnest(data, cols, ..., keep_empty = FALSE, ptype = NULL, names_sep = NULL, names_repair = "check_unique", .drop = deprecated(), .id = deprecated(), .sep = deprecated(), .preserve = deprecated())

而在?hoist的描述中是这样的:

hoist(),unnest_longer()和unnest_wider()提供了将深度嵌套的列表转换为常规列的工具。

此外,如果意图不是获取中间的宽格式,只需使用unnest_longer
dat %>%
      unnest_longer(df_list)
# A tibble: 13 x 3
#   id      some_date  df_list  
#   <chr>   <chr>      <chr>    
# 1 509935  2/09/1967  018084131
# 2 727889  28/04/1976 062197171
# 3 727889  28/04/1976 062171593
# 4 864607  22/12/2017 064601923
# 5 864607  22/12/2017 068994009
# 6 864607  22/12/2017 069831651
# 7 1234243 7/02/2006  071141584
# 8 1234243 7/02/2006  073129537
# 9 1020959 10/03/2019 061498574
#10 1020959 10/03/2019 065859718
#11 1020959 10/03/2019 067251995
#12 1020959 10/03/2019 069447806
#13 221975  21/10/1935 064623976

或者使用 base R

merge(setNames(stack(setNames(dat$df_list, dat$id))[2:1], 
      c("id", "values")), dat[-3])

2
你可以通过将更改直接包含在 names_repair 参数中来避免使用 rename_at()unnest_wider(dat, df_list, names_repair = ~ str_remove(str_c("res", .x), "[.]+")) - Phil
1
@akrun - 我觉得今天早上通过 unnest_wider 和 pivot_long 学到了很多,这些会帮助很多。 - John
我认为 unnest 作为一个函数并没有被弃用,但是一些传递给 unnest 的参数已经被弃用了。 - Ronak Shah

3
如果最终目标是获取长格式的数据,我们可以使用 tidyr 中的 unnest
tidyr::unnest(dat, cols = df_list)

#   id      some_date  df_list  
#   <chr>   <chr>      <chr>    
# 1 509935  2/09/1967  018084131
# 2 727889  28/04/1976 062197171
# 3 727889  28/04/1976 062171593
# 4 864607  22/12/2017 064601923
# 5 864607  22/12/2017 068994009
# 6 864607  22/12/2017 069831651
# 7 1234243 7/02/2006  071141584
# 8 1234243 7/02/2006  073129537
# 9 1020959 10/03/2019 061498574
#10 1020959 10/03/2019 065859718
#11 1020959 10/03/2019 067251995
#12 1020959 10/03/2019 069447806
#13 221975  21/10/1935 064623976

1

基本的 R 解决方案:

# Split, Apply, Combine Base R: 
# Split the data frame on ids, unlist the dataframe list, replicated the id,
# the number of times as there are elements in the unlisted df list - store
# as a dataframe, left join back to the original data.frame,
# (dropping the df_list vector) using the ID vector, row bind the id data.frames
# back together and store it as a dataframe: 

data.frame(do.call("rbind", lapply(split(df, df$id), function(x){

      unlisted_df_list <- unlist(x$df_list)

      rolled_out_df <- data.frame(id = rep(x$id, length(unlisted_df_list)),

                                 df_list = unlisted_df_list, stringsAsFactors = F)

      x <- merge(x[,names(x) != "df_list"], rolled_out_df, by = "id", all.x = T)

      }

    )

  ),

  row.names = NULL

)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接