r - 展开包含 <list> 观察值的 data.table 的多列

标签 r data.table rbindlist

我有一个 data.table,其中超过 2 列的类型为 list。我想扩展这些列,以便列表中的每个元素都成为一个新列。我想要一种比“手动”扩展每列然后将表连接在一起更优雅的方法。

设置

编辑:(提供我从中获取data.tablejson)

所以我有一个像这样的 json 文件:

[
    {
        "origins": [
            {
                "orig_lon": "14.36784",
                "orig_lat": "49.985982",
                "local_id": "AD.22045279",
                "full_address": "Věštínská 36/9, Radotín, 15300 Praha 5"
            },
            {
                "orig_lon": "14.352792",
                "orig_lat": "49.983317",
                "local_id": "AD.22055428",
                "full_address": "Otínská 1102/37, Radotín, 15300 Praha 5"
            }
        ],
        "destinations": [
            {
                "dest_lon": "14.352245",
                "dest_lat": "49.981314",
                "local_id": "AD.22045848",
                "full_address": "Zderazská 98/3, Radotín, 15300 Praha 5"
            },
            {
                "dest_lon": "14.226975",
                "dest_lat": "50.051702",
                "local_id": "AD.27261433",
                "full_address": "Západní 458, 25303 Chýně"
            }
        ],
        "destination_addresses": [
            "Zderazská 98/3, 153 00 Praha-Radotín, Czechia",
            "Západní 458, 253 01 Chýně, Czechia"
        ],
        "origin_addresses": [
            "U Jankovky 455/18, 153 00 Praha-Radotín, Czechia",
            "Otínská 1102/37, 153 00 Praha-Radotín, Czechia"
        ],
        "rows": [
            {
                "elements": [
                    {
                        "distance": {
                            "text": "1.6 km",
                            "value": 1620
                        },
                        "duration": {
                            "text": "5 mins",
                            "value": 272
                        },
                        "duration_in_traffic": {
                            "text": "5 mins",
                            "value": 277
                        },
                        "status": "OK"
                    },
                    {
                        "distance": {
                            "text": "19.3 km",
                            "value": 19313
                        },
                        "duration": {
                            "text": "22 mins",
                            "value": 1343
                        },
                        "duration_in_traffic": {
                            "text": "24 mins",
                            "value": 1424
                        },
                        "status": "OK"
                    }
                ]
            },
            {
                "elements": [
                    {
                        "distance": {
                            "text": "0.7 km",
                            "value": 691
                        },
                        "duration": {
                            "text": "2 mins",
                            "value": 101
                        },
                        "duration_in_traffic": {
                            "text": "2 mins",
                            "value": 99
                        },
                        "status": "OK"
                    },
                    {
                        "distance": {
                            "text": "18.7 km",
                            "value": 18655
                        },
                        "duration": {
                            "text": "21 mins",
                            "value": 1246
                        },
                        "duration_in_traffic": {
                            "text": "22 mins",
                            "value": 1336
                        },
                        "status": "OK"
                    }
                ]
            }               
        ],
        "status": "OK"
    },
    {
        "origins": [
            {
                "orig_lon": "14.36784",
                "orig_lat": "49.985982",
                "local_id": "AD.22045279",
                "full_address": "Věštínská 36/9, Radotín, 15300 Praha 5"
            },
            {
                "orig_lon": "14.352792",
                "orig_lat": "49.983317",
                "local_id": "AD.22055428",
                "full_address": "Otínská 1102/37, Radotín, 15300 Praha 5"
            }
        ],
        "destinations": [
            {
                "dest_lon": "14.36053",
                "dest_lat": "49.981687",
                "local_id": "AD.22047131",
                "full_address": "Zítkova 235/7, Radotín, 15300 Praha 5"
            },
            {
                "dest_lon": "14.361052",
                "dest_lat": "49.988529",
                "local_id": "AD.22054952",
                "full_address": "Strážovská 1053/33, Radotín, 15300 Praha 5"
            }
        ],
        "destination_addresses": [
            "Zítkova 235/7, 153 00 Praha-Radotín, Czechia",
            "Strážovská 1053/33, 153 00 Praha-Radotín, Czechia"
        ],
        "origin_addresses": [
            "U Jankovky 455/18, 153 00 Praha-Radotín, Czechia",
            "Otínská 1102/37, 153 00 Praha-Radotín, Czechia"
        ],
        "rows": [
            {
                "elements": [
                    {
                        "distance": {
                            "text": "1.4 km",
                            "value": 1445
                        },
                        "duration": {
                            "text": "4 mins",
                            "value": 248
                        },
                        "duration_in_traffic": {
                            "text": "4 mins",
                            "value": 247
                        },
                        "status": "OK"
                    },
                    {
                        "distance": {
                            "text": "1.9 km",
                            "value": 1933
                        },
                        "duration": {
                            "text": "4 mins",
                            "value": 264
                        },
                        "duration_in_traffic": {
                            "text": "4 mins",
                            "value": 267
                        },
                        "status": "OK"
                    }
                ]
            },
            {
                "elements": [
                    {
                        "distance": {
                            "text": "1.4 km",
                            "value": 1374
                        },
                        "duration": {
                            "text": "4 mins",
                            "value": 232
                        },
                        "duration_in_traffic": {
                            "text": "4 mins",
                            "value": 241
                        },
                        "status": "OK"
                    },
                    {
                        "distance": {
                            "text": "1.3 km",
                            "value": 1274
                        },
                        "duration": {
                            "text": "3 mins",
                            "value": 167
                        },
                        "duration_in_traffic": {
                            "text": "3 mins",
                            "value": 174
                        },
                        "status": "OK"
                    }
                ]
            }
        ],
        "status": "OK"
    }
]

我读到的内容如下:

library(jsonlite)
library(data.table)
data <- read_json('./path_to_that_json/that_json.json')

这会产生长度为 2 的列表

我可以将其隐藏到data.table中,例如:

dt <- rbindlist(lapply(data, as.data.table))

这会产生一个data.table,如下所示:

   origins destinations                             destination_addresses                                 origin_addresses
1:  <list>       <list>     Zderazská 98/3, 153 00 Praha-Radotín, Czechia U Jankovky 455/18, 153 00 Praha-Radotín, Czechia
2:  <list>       <list>                Západní 458, 253 01 Chýne, Czechia   Otínská 1102/37, 153 00 Praha-Radotín, Czechia
3:  <list>       <list>      Zítkova 235/7, 153 00 Praha-Radotín, Czechia U Jankovky 455/18, 153 00 Praha-Radotín, Czechia
4:  <list>       <list> Strážovská 1053/33, 153 00 Praha-Radotín, Czechia   Otínská 1102/37, 153 00 Praha-Radotín, Czechia
     rows status
1: <list>     OK
2: <list>     OK
3: <list>     OK
4: <list>     OK

这意味着我有几个包含列表的列,我想扩展它们。

什么样的工作

我知道要扩展一列,我可以这样做:

dt[, r = as.character(.I)]
res1 <- dt[, rbindlist(setNames(origins, r), id = "r")]

(我在这里发现:Expand list column of data.tables)

现在,我可以通过重复此调用并使用列 r 连接结果来扩展多个列。这可能看起来像:

res1 <- dt[dt[, rbindlist(origins, id = "r")][
  , `:=`(r=as.character(r))], on = "r"][, `:=`(origins = NULL, destinations = NULL)][dt[
    , rbindlist(destinations, id = "r")][
      , `:=`(r=as.character(r))], on = "r"]

这会给我所需的输出:

                               destination_addresses                                 origin_addresses   rows status r
1:     Zderazská 98/3, 153 00 Praha-Radotín, Czechia U Jankovky 455/18, 153 00 Praha-Radotín, Czechia <list>     OK 1
2:                Západní 458, 253 01 Chýne, Czechia   Otínská 1102/37, 153 00 Praha-Radotín, Czechia <list>     OK 2
3:      Zítkova 235/7, 153 00 Praha-Radotín, Czechia U Jankovky 455/18, 153 00 Praha-Radotín, Czechia <list>     OK 3
4: Strážovská 1053/33, 153 00 Praha-Radotín, Czechia   Otínská 1102/37, 153 00 Praha-Radotín, Czechia <list>     OK 4
    orig_lon  orig_lat    local_id                            full_address  dest_lon  dest_lat  i.local_id
1:  14.36784 49.985982 AD.22045279  Veštínská 36/9, Radotín, 15300 Praha 5 14.352245 49.981314 AD.22045848
2: 14.352792 49.983317 AD.22055428 Otínská 1102/37, Radotín, 15300 Praha 5 14.226975 50.051702 AD.27261433
3:  14.36784 49.985982 AD.22045279  Veštínská 36/9, Radotín, 15300 Praha 5  14.36053 49.981687 AD.22047131
4: 14.352792 49.983317 AD.22055428 Otínská 1102/37, Radotín, 15300 Praha 5 14.361052 49.988529 AD.22054952
                               i.full_address
1:     Zderazská 98/3, Radotín, 15300 Praha 5
2:                   Západní 458, 25303 Chýne
3:      Zítkova 235/7, Radotín, 15300 Praha 5
4: Strážovská 1053/33, Radotín, 15300 Praha 5

我的问题是:

是否有更优雅、更高效的方式来扩展多个列?理论上,我希望有一个要扩展的列列表,然后进行一次调用来扩展所有列并返回上述结果。

此外,对于rows列,扩展有点复杂:到目前为止,我正在创建一个list类型的新列,其中不包括状态记录。像这样的东西:

dt[, rows2 := lapply(rows, function(x) list("distance" = (x[[1]][[1]]["distance"]),
                                         "duration" = (x[[1]][[1]]["duration"]),
                                         "duration_in_traffic" = (x[[1]][[1]]["duration_in_traffic"])))]

然后可以使用上述过程将 rows2 扩展为 list 类型的三列,随后可以使用相同的过程对其进行扩展。现在,这种方法很糟糕,原因很明显,因为对于在我之后阅读代码的任何人来说都不是很简单。此外,还需要大量打字。我认为一定有更优雅的方式来解决这个问题。

最佳答案

因此,考虑该问题的一种方法是使用 lapply 处理列表列,以分别扩展每个列并将其存储到 data.tables 列表中,然后立即合并列表中的所有列。

要创建扩展变量列表,您只需执行以下操作:

    expandcols<-c("origins","destinations")

    lapply(expandcols,function(i) rbindlist(dt[[i]],idcol = "r")))

另请注意,您的原始 r 列是一个字符向量,而 rbindlist 创建的 idcol 是一个整数,因此您在这里需要一致性。在我的代码中,我刚刚将您的原始数据转换为数字。

要合并 data.tables 列表,我喜欢使用像这样的 Reduce 函数:

     Reduce(function(...) merge(...,by="keys"), list())

输出将是一个 data.table,其中键列是“r”,该列表将是上面 lapply 调用的结果。然后,您可以通过 data.table 方式将结果与原始数据框合并。总而言之,调用将如下所示:

    dtfinal<-Reduce(function(...) merge(...,by="r"),lapply(expandcols,function(i) rbindlist(dt[[i]],idcol = "r")))[dt[,-expandcols,with=F],on="r"]

这是我制作的函数的代码:

    list_expander_fn<-function(X){
      '%notin%'<-Negate('%in%')##Helpful for selecting column names later
      expandcols_fun<-function(Y){##Main function to be called recursively as needed and takes in a data.table object as its only argument.
        listcols<-colnames(Y)[which(sapply(Y,is.list))] #Identify list columns
        listdt<-lapply(listcols,function(i) tryCatch(rbindlist(Y[[i]],idcol = "r"),error=function(e) NULL)) #Expand lists using rbindlist and returns null on error.

        invalidlists<-which(sapply(listdt,is.null)) #Rbindlist does not work unless list elements contain data.tables

##Simply unlists if character vector is created like in destination and origin addresses columns
        if(length(invalidlists)!=0){
            Y[,listcols[invalidlists]:=lapply(.SD,unlist),.SDcols = listcols[invalidlists]]

            listcols<-listcols[-invalidlists] ##Update list columns to be merged
            listdt<-listdt[-invalidlists]##Removes NULL elements from the listdt.
        }

        origcols<-colnames(Y)[colnames(Y)%notin%listcols]##Identifies  nonlist columns for final merge
        currentdt<-Reduce(function(...) merge(...,by="r"),listdt) ##merges list of data.tables
        return(currentdt[Y[,origcols,with=F],on="r"])
        }

      repeat{
        currentexpand<-expandcols_fun(X) #Executes the expandcols_fun
        listcheck<-sapply(currentexpand,is.list) #Checks again if lists still exist
        if(sum(listcheck)!=0){
          X<-currentexpand #Updates the X for recursive calls

        } else{
          break
        }
      }

      return(currentexpand)
}

它可以工作,但由于最终的字段名称(文本和值),变量名称存在问题。如果你喜欢它的发展方向,我可能会稍微修改一下。它适用于“rows2”,但不适用于“rows”。调用它的代码当然很简单:

    finaldt<-list_expander_fn(dt)

这有助于回答您的问题吗?如果您希望我在解释中添加任何内容,请告诉我。祝你好运!

关于r - 展开包含 <list> 观察值的 data.table 的多列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53969000/

相关文章:

R data.table : how to go from tibble to data. 表返回?

r - 如何在R中的巨大数据集中计算两个变量的相关性?

r - 将 stargazer 与 Zelig 一起使用

r - 在数据框中查找最小转折点

r - 为什么rbindlist是 "better"而不是rbind?

r - 使用 rbindlist 时遇到错误 : column 25 of result is determined to be integer64 but maxType == 'Character' ! =REALSXP

r - 在R中的字符串后提取一定数量的单词或特殊字符

r - data.table 和 table 意外行为

r - 按组平均,删除当前行