【问题标题】:From list to data frame with tidyverse, selecting specific list elements使用 tidyverse 从列表到数据框,选择特定的列表元素
【发布时间】:2020-04-08 03:36:48
【问题描述】:

一个简单的问题,但我已经搜索了解决方案,但到目前为止无济于事。

假设我有一个列表对象,我想提取特定的列表元素并将它们并排输出为数据框列。如何以简单的方式通过 tidyverse/管道实现这一点?尝试在下面解决它。

数据

some_data <-
structure(list(x = c(23.7, 23.41, 23.87, 24.18, 24.15, 24.31, 
23.14, 23.72, 24.12, 23.47, 23.59, 23.29, 23.24, 23.5, 23.56, 
23.16, 23.62, 23.67, 23.84, 23.69, 23.7, 23.68, 24.2, 23.77, 
23.74, 23.64, 24.39, 24.05, 24.51, 23.6, 24.29, 23.31, 23.96, 
24.07, 24.37, 23.77, 23.64, 24, 23.68, 24.02, 23.36, 23.54, 23.34, 
23.69, 23.79, 23.8, 23.7, 24.45, 23.27, 23.57, 23.02, 24.23, 
23.41, 23.6, 24.02, 23.94, 24.06, 23.97, 23.38, 23.46, 24, 23.89, 
23.51, 23.72, 23.83, 23.96, 23.84, 23.52, 24.36, 23.94, 23.82, 
24.04, 24.05, 23.6, 23.52, 24.13, 23.43, 23.33, 24.01, 23.99, 
24.46, 24.23, 24.19, 23.83, 23.8, 23.93, 23.79, 23.48, 23.26, 
24.04, 23.93, 23.98, 23.86, 23.49, 24.17, 23.7, 23.54, 23.55, 
23.67, 23.66)), class = c("spec_tbl_df", "tbl_df", "tbl", "data.frame"
), row.names = c(NA, -100L), spec = structure(list(cols = list(
    x = structure(list(), class = c("collector_double", "collector"
    ))), default = structure(list(), class = c("collector_guess", 
"collector")), skip = 1), class = "col_spec"))

我想要这个数据的`hist()`函数的值输出

library(tidyverse)

some_data$x %>% 
   as.numeric() %>% 
   hist(breaks = seq(from = 23, to = 24.6, by = 0.2),
        plot = FALSE)

## $breaks
## [1] 23.0 23.2 23.4 23.6 23.8 24.0 24.2 24.4 24.6

## $counts
## [1]  3  9 20 23 19 16  7  3

## $density
## [1] 0.15 0.45 1.00 1.15 0.95 0.80 0.35 0.15

## $mids
## [1] 23.1 23.3 23.5 23.7 23.9 24.1 24.3 24.5

## $xname
## [1] "."

## $equidist
## [1] TRUE

## attr(,"class")
## [1] "histogram"

假设我希望将 `$breaks` 和 `$counts` 并排作为一个数据框

我将补充原来的管道,以便:

some_data$x %>% 
   as.numeric() %>% 
   hist(breaks = seq(from = 23, to = 24.6, by = 0.2),
        plot = FALSE) %>%
##
   map_df(~.[1:30]) %>%
   select(bins = breaks, 
          frequency = counts)
##

## # A tibble: 30 x 2
##     bins frequency
##    <dbl>     <int>
##  1  23           3
##  2  23.2         9
##  3  23.4        20
##  4  23.6        23
##  5  23.8        19
##  6  24          16
##  7  24.2         7
##  8  24.4         3
##  9  24.6        NA
## 10  NA          NA
## # ... with 20 more rows

所以是的,它确实有效,但是在map_df() 中,我必须输入一个相对较大的“神奇”数字(我随意输入 30)以确保包含所有数据。有没有更简单的方法来获取 $breaks$counts 作为数据框?或许只需一步而不是map_df()select() 结合起来?

评论

虽然这个特定问题展示了histogram 类的情况,但我的一般问题不是关于直方​​图,而是关于列表对象的原则。 hist(plot = FALSE) 的输出的好处在于它生成了一个包含不等长元素的对象,这说明了一个需要灵活解决方案来解决元素长度变化的问题。

解决方案

基于下面 Rémi Coulaud 的(选择的)解决方案,解决列表元素长度不等的情况的方法是使它们相等,锚定到最长的元素。然后,这不再是问题了。工作管道如下:

library(tidyverse)

some_data$x %>% 
  as.numeric() %>% 
  hist(breaks = seq(from = 23, to = 24.6, by = 0.2),
       plot = FALSE) %>%
  lapply(., `length<-`, max(lengths(.))) %>%  ## make all elements as the length of the longest one
  map_df(~.) %>%
  select(bins = breaks, 
         frequency = counts)

谢谢!

【问题讨论】:

    标签: r tidyverse


    【解决方案1】:

    我们可以使用imapenframe 将列表中的每个元素转换为带有name(行号)和value(元素名称)的数据框。然后我们可以使用reducefull_join 来连接所有数据帧。最后,我们可以选择我们想要的列。这种方法不需要指定一个“幻数”。

    library(tidyverse)
    
    some_data$x %>% 
      as.numeric() %>% 
      hist(breaks = seq(from = 23, to = 24.6, by = 0.2),
           plot = FALSE) %>%
      imap(~enframe(.x, value = .y)) %>%
      reduce(full_join, by = "name") %>%
      select(bins = breaks, 
             frequency = counts)
    # # A tibble: 9 x 2
    #   bins frequency
    #   <dbl>     <int>
    # 1  23           3
    # 2  23.2         9
    # 3  23.4        20
    # 4  23.6        23
    # 5  23.8        19
    # 6  24          16
    # 7  24.2         7
    # 8  24.4         3
    # 9  24.6        NA
    

    【讨论】:

    • 谢谢。虽然结果是我正在寻找的,但我希望有一个更简单的方法。为了避免幻数,有没有办法引用最长列表元素的长度,同时仍在管道内?一个例子(不工作)是:some_data$x %&gt;% as.numeric() %&gt;% hist(breaks = seq(from = 23, to = 24.6, by = 0.2), plot = FALSE) %&gt;% map_df(~.[1:max(lengths(.))]) 我们可以调整这个1:max(lengths(.)) 的东西吗?
    【解决方案2】:

    部分复杂因素是hist() 对象的列表具有不同的长度:

    library(tidyverse)
    
    brks <- seq(from = 23, to = 24.6, by = 0.2)
    
    hist_res <- some_data$x %>% 
      as.numeric() %>% 
      hist(breaks = brks,
           plot = FALSE)
    
    lengths(hist_res)
    
      breaks   counts  density     mids    xname equidist 
           9        8        8        8        1        1 
    

    OP 评论说,不均匀列表是问题的主要部分。我们需要做出选择或规则来确定为data.frame 选择了哪些列表元素。在这种情况下,我们可以使用table()which() 和基数[ 的组合来选择最常见的长度。对于这个hist() 示例,我仍然在mutate 调用中手动操作breaks 列:

    l <- lengths(hist_res)
    cols <- which(l == as.integer(names(table(l)))[which.max(table(l))])
    
    hist_res%>%
      .[cols]%>%
      as_tibble()%>%
      mutate(brk_start = brks[-length(brks)],
             brk_end = brks[-1])
    
    # A tibble: 8 x 5
      counts density  mids brk_start brk_end
       <int>   <dbl> <dbl>     <dbl>   <dbl>
    1      3   0.15   23.1      23      23.2
    2      9   0.45   23.3      23.2    23.4
    3     20   1.000  23.5      23.4    23.6
    4     23   1.15   23.7      23.6    23.8
    5     19   0.95   23.9      23.8    24  
    6     16   0.8    24.1      24      24.2
    7      7   0.35   24.3      24.2    24.4
    8      3   0.150  24.5      24.4    24.6
    

    【讨论】:

    • 谢谢。你是对的,复杂性在于列表元素的长度不等。您的解决方案很聪明,但它缩小到我在问题中指定的 2 个元素。我正在寻找一个更广泛的解决方案来解决列表元素的任何子集。
    • 查看编辑。这很复杂——在没有规则的情况下很难以编程方式处理不同长度的列表。我也走出了管道,但可能有办法让管道正常工作。
    【解决方案3】:

    关于histogram的第一个问题我找到的最佳答案是here

    我正在尝试做同样的事情,确实你不需要使用hist 函数,因为最后你想要一个data.frame

    一种解决方案是:

    library(tidyverse)
    breaks <- seq(from = 23, to = 24.6, by = 0.2)
    df <- data.frame(breaks = breaks,
               frequency = c(some_data$x %>% 
      as.numeric() %>%
      findInterval(vec = breaks) %>%
      tabulate(), NA))
    
    df
    

    NA 是必需的,因为您的计数少于中断值。

    编辑 1

    必须考虑hist 类的特殊性。就像说@Cole。如果您想要列表对象的解决方案,您应该查看下面的答案。

    如果您的问题只是从list 传递到data.frame。选择仅包含list 的示例可能更合适。此外,如果我们没有从 hist 类传递到 data.frame 的问题。没有问题。实际上,r 中的listdata.frame 相同。所以你可以这样做:

    library(dplyr)
    l <- list(breaks = c(1, 2, 3, 4),
              counts = c(10, 34, 54, 78),
              other = rep("A", 4))
    

    如果需要小标题:

    l %>% as_tibble %>% select(breaks:counts)
    

    如果你想要一个data.frame:

    l %>% data.frame
    

    我希望它能澄清你的问题。

    编辑 2

    对于具有不等长度元素的list,请参阅there。一世 lengths 为您提供list 的每个元素的长度。将所有元素标准化为相同大小后:

    lapply(l, `length<-`, max(lengths(l)))
    

    您只需绑定它们并将其转换为 data.frame。您可以在整个管道中使用 dplyr 语法,但它也可以这样工作:

    as.data.frame(do.call(cbind, lapply(l, `length<-`, max(lengths(l)))))
    

    带管道:

    lapply(l, `length<-`, max(lengths(l))) %>%
      do.call(what = cbind) %>%
      data.frame
    

    总之,似乎必须指定最大 length,然后再创建一个 data.frame

    length&lt;- 参见there,函数为您提供从开始到您给出的值的所有元素,在我的示例中为 5。如果您的向量较短,它会自动引入 NA 值。

    例如:

    l <- list(breaks = c(1, 2, 3, 4),
              counts = c(10, 34, 54, 78),
              other = rep("A", 4),
              diff = rep("B", 3))
    
    `length<-`(l$breaks, 5)
    [1]  1  2  3  4 N
    

    【讨论】:

    • 谢谢。你是对的,直方图,可能有很多其他方法可以获得相同的结果。但是,我使用这个带有hist().just 的案例示例来说明我关于提取列表元素并将它们安排在数据框中的普遍问题,同时考虑到元素长度不等的情况。这就是为什么这个问题没有被框定为直方图问题。
    • 谢谢!我基本上只采用了您提出的lapply(l, `length&lt;-`, max(lengths(l))) 行并在我的管道中实现了它,它解决了所有问题。我将编辑帖子以反映解决方案的机制。
    • 很高兴能为您提供帮助,但这主要归功于akrun的回答。
    猜你喜欢
    • 2020-11-15
    • 1970-01-01
    • 2017-06-08
    • 2022-10-14
    • 1970-01-01
    • 1970-01-01
    • 2021-11-10
    • 2018-08-17
    • 1970-01-01
    相关资源
    最近更新 更多