Разверните несколько столбцов таблицы data.table, содержащей <list>наблюдений - PullRequest
0 голосов
/ 29 декабря 2018

У меня есть data.table, где более 2 столбцов имеют тип list.Я хотел бы расширить эти столбцы, чтобы каждый элемент списка становился новым столбцом.Мне бы хотелось иметь более элегантный способ, чем «вручную» расширять каждый столбец и затем объединять таблицы.

настройка

Редактировать: (предоставив json, из которого я получил data.table)

Итак, у меня есть файл 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')

Это приводит к list длины 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")]

(я нашел это здесь: Развернуть список столбцов 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, который не включает запись status.Что-то вроде:

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, которые впоследствии могут быть расширены с использованием той же процедуры.Теперь этот подход отстой по очевидной причине, поскольку он не очень прост для тех, кто читает код после меня.Кроме того, требуется много печатать.Я думаю, что должен быть более элегантный способ разобраться в этом.

Ответы [ 2 ]

0 голосов
/ 30 декабря 2018

Вместо споров в data.table рассмотрите возможность создания таблицы данных из объекта данных json, который часто импортируется как сильно вложенный список фреймов данных или другие списки.В результате вам необходимо выполнить миграцию в соответствии с путями элементов разного уровня:

library(jsonlite)
library(data.table)

json_data <- read_json('/path/to/posted.json')

df_list <- lapply(json_data, function(item)
  data.frame(origin_address = unlist(item$origin_addresses),           # TOP LEVEL
             destination_address = unlist(item$destination_addresses), # TOP LEVEL
             do.call(rbind, lapply(item$origins, data.frame)),         # NESTED LEVEL
             do.call(rbind, lapply(item$destinations, data.frame)))    # NESTED LEVEL
)

final_df <- do.call(rbind, df_list)  # SINGLE DATA FRAME
final_dt <- rbindlist(df_list)       # SINGLE DATA TABLE

Вывод (обязательно переименуйте поля full_address и local_id в origin_ или destination_)

final_dt

#                                      origin_address                               destination_address  orig_lon  orig_lat    local_id
# 1: U Jankovky 455/18, 153 00 Praha-Radotín, Czechia     Zderazská 98/3, 153 00 Praha-Radotín, Czechia  14.36784 49.985982 AD.22045279
# 2:   Otínská 1102/37, 153 00 Praha-Radotín, Czechia                Západní 458, 253 01 Chýně, Czechia 14.352792 49.983317 AD.22055428
# 3: U Jankovky 455/18, 153 00 Praha-Radotín, Czechia      Zítkova 235/7, 153 00 Praha-Radotín, Czechia  14.36784 49.985982 AD.22045279
# 4:   Otínská 1102/37, 153 00 Praha-Radotín, Czechia Strážovská 1053/33, 153 00 Praha-Radotín, Czechia 14.352792 49.983317 AD.22055428
#                               full_address  dest_lon  dest_lat  local_id.1                             full_address.1
# 1:  Věštínská 36/9, Radotín, 15300 Praha 5 14.352245 49.981314 AD.22045848     Zderazská 98/3, Radotín, 15300 Praha 5
# 2: Otínská 1102/37, Radotín, 15300 Praha 5 14.226975 50.051702 AD.27261433                   Západní 458, 25303 Chýně
# 3:  Věštínská 36/9, Radotín, 15300 Praha 5  14.36053 49.981687 AD.22047131      Zítkova 235/7, Radotín, 15300 Praha 5
# 4: Otínská 1102/37, Radotín, 15300 Praha 5 14.361052 49.988529 AD.22054952 Strážovská 1053/33, Radotín, 15300 Praha 5
0 голосов
/ 29 декабря 2018

Таким образом, один из способов решения проблемы состоит в том, чтобы обработать столбцы списка, используя lapply, чтобы развернуть каждый из них по отдельности и сохранить в список data.tables, а затем объединить все из списка сразу.

Чтобы создать список расширенных переменных, вы просто должны сделать следующее:

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

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

Также обратите внимание, что ваш исходный столбец r является символьным вектором, а idcol, созданный rbindlist, является целым числом, поэтомувам понадобится последовательность здесь.В моем коде я только что преобразовал ваш оригинал в числовой.

Чтобы объединить список data.tables, мне нравится использовать функцию Reduce следующим образом:

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

Выходными данными будут одни данные.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)
}

Это работает, но есть проблемы с именами переменных из-заокончательные имена полей (текст и значение).Я мог бы, вероятно, немного повозиться с этим, если вам нравится, где это происходит.Работает с 'row2', но не с 'rows'.Код для его вызова будет, конечно, простым:

    finaldt<-list_expander_fn(dt)

Помогает ли это ответить на ваш вопрос?Дайте мне знать, если вы хотите, чтобы я добавил что-нибудь к объяснению.Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...