簡體   English   中英

將具有可變數量的元素的列表的嵌套列表展平到數據框中

[英]Flatten nested list of lists with variable numbers of elements to a data frame

我有一個嵌套的列表列表,我想將其平鋪到帶有id變量的數據框中,以便知道每個列表元素(和子列表元素)來自哪個列表元素。

> str(gc_all)
List of 3
$ 1: num [1:102, 1:2] -74 -73.5 -73 -72.5 -71.9 ...
..- attr(*, "dimnames")=List of 2
.. ..$ : NULL
.. ..$ : chr [1:2] "lon" "lat"
$ 2: num [1:102, 1:2] -74 -73.3 -72.5 -71.8 -71 ...
..- attr(*, "dimnames")=List of 2
.. ..$ : NULL
.. ..$ : chr [1:2] "lon" "lat"
$ 3:List of 2
..$ : num [1:37, 1:2] -74 -74.4 -74.8 -75.3 -75.8 ...
.. ..- attr(*, "dimnames")=List of 2
.. .. ..$ : NULL
.. .. ..$ : chr [1:2] "lon" "lat"
..$ : num [1:65, 1:2] 180 169 163 158 154 ...
.. ..- attr(*, "dimnames")=List of 2
.. .. ..$ : NULL
.. .. ..$ : chr [1:2] "lon" "lat"

我之前曾使用plyr::ldply(mylist, rbind)來使列表變平,但是由於列表長度可變,我似乎遇到了麻煩:一些列表元素僅包含一個數據幀,而其他列表元素包含兩個數據幀的列表。

我發現使用兩個lapply和一個ifelse這樣的笨拙解決方案, ifelse所示:

# sample latitude-longitude data
df <- data.frame(source_lat = rep(40.7128, 3),
                 source_lon = rep(-74.0059, 3),
                 dest_lat = c(55.7982, 41.0082, -7.2575),
                 dest_lon = c(37.968, 28.9784, 112.7521),
                 id = 1:3)

# split into list
gc_list <- split(df, df$id)

# get great circles between lat-lon for each id; multiple list elements are outputted when the great circle crosses the dateline
gc_all <- lapply(gc_list, function(x) {
  geosphere::gcIntermediate(x[, c("source_lon", "source_lat")],
                 x[, c("dest_lon", "dest_lat")],
                 n = 100, addStartEnd=TRUE, breakAtDateLine=TRUE)
})

gc_fortified <- lapply(1:length(gc_all), function(i) {
  if(class(gc_all[[i]]) == "list") {
    lapply(1:length(gc_all[[i]]), function(j) {
      data.frame(gc_all[[i]][[j]], id = i, section = j)
    }) %>%
      plyr::rbind.fill()
  } else {
    data.frame(gc_all[[i]], id = i, section = 1)
  }
}) %>%
  plyr::rbind.fill()

但是我覺得必須有一個更優雅的解決方案,像dputdata.table

這是我期望輸出看起來像的樣子:

> gc_fortified %>% 
    group_by(id, section) %>%
    slice(1)

lon      lat    id section
<dbl>    <dbl> <int>   <dbl>
1 -74.0059 40.71280     1       1
2 -74.0059 40.71280     2       1
3 -74.0059 40.71280     3       1
4 180.0000 79.70115     3       2

我想我更喜歡已經顯示的遞歸解決方案,但是如果將Ladd_n_s到最后一行,這是所要求的do.call("rbind", ...)形式的一個語句。 為了清楚起見,我在這里將它們分開。

由於結果是完全數字的,因此我將結果保留為矩陣,我懷疑不是您更喜歡數據幀,而是rbind.fill在它們上起作用,而這正是您使用的數據。 更換cbindadd_n_s與功能data.frame如果你喜歡一個數據幀的結果。

不使用任何軟件包,該解決方案不使用任何索引。

在這里, gc_all轉換為L ,除了它是列表的列表而不是矩陣和列表的混合列表之外, L相同。 add_n_s采用L的元素,並向其中添加ns列。 最后,我們將add_n_sL並展平。

注意,如果輸入首先是列表列表,則L等於gc_all並且不需要第一行。

L <- lapply(gc_all, function(x) if (is.list(x)) x else list(x))

add_n_s <- function(x, n) Map(cbind, x, n = n, s = seq_along(x))
do.call("rbind", do.call("c", Map(add_n_s, L, seq_along(gc_all))))

更新固定。

我無法提供單線服務,但您也可以在這里考慮遞歸

flat <- function(l, s = NULL) {
  lapply(1:length(l), function(i) {
    if (is.list(l[[i]])) {
      do.call(rbind, flat(l[[i]], i))
    } else {
      cbind(l[[i]], id = if (is.null(s)) i else s, section = if (is.null(s)) 1 else i)
    }
  })
}

a <- do.call(rbind, flat(gc_all))
all.equal(data.frame(a), gc_fortified)

[1] TRUE

首先,需要對列表的結構進行重新map_dfr ,使其成為常規的列表列表,然后使用.id參數應用map_dfr兩次。

library(purrr)
gc_all_df  <- map(map_if(gc_all,~class(.x)=="matrix",list),~map(.x,as.data.frame))
map_dfr(gc_all_df,~map_dfr(.x,identity,.id="id2"),identity,.id="id1")

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM