Извлеките несколько столбцов из списка списков и сохраните в data.frame

У меня есть следующий список:

library(rjson)
j <- fromJSON(file='https://esgf-data.dkrz.de/esg-search/search/?offset=0&limit=1000&type=Dataset&replica=false&latest=true&project=CORDEX&domain=EUR-11&experiment=rcp85&time_frequency=day&facets=rcm_name%2Cproject%2Cproduct%2Cdomain%2Cinstitute%2Cdriving_model%2Cexperiment%2Cexperiment_family%2Censemble%2Crcm_version%2Ctime_frequency%2Cvariable%2Cvariable_long_name%2Ccf_standard_name%2Cdata_node&format=application%2Fsolr%2Bjson')

Меня интересует извлечение данных из этого компонента: j$response$docs, который представляет собой список списков. Все «внутренние» списки должны иметь одинаковые имена.

Я хочу сохранить вывод в data.frame() или tibble().

Это ниже работает и дает желаемый результат для нескольких выбранных переменных:

nmod <- length(j$response$docs)
for (i in 1:nmod) {
    #select one list at a time
    j1 <- j$response$docs[[i]]
    tmp <- data.frame(variable=j1$variable,
                        variable_long_name=j1$variable_long_name,
                        rcm_name=j1$rcm_name,
                        driving_model=j1$driving_model,
                        cf_standard_name=j1$cf_standard_name
                        )
    #join them
    if (i==1) {
        d <- tmp
    } else {
        d <- rbind(d, tmp)
    }
}

Однако я хотел бы знать, есть ли более элегантный и эффективный способ, возможно, с использованием tidyr, dplyr или purrr, который также позволил бы мне выбрать все «столбцы», а не только несколько выбранных там.


person AF7    schedule 20.04.2017    source источник
comment
связывание списка вместе можно привести в порядок, используя bind_rows из dplyr, который ожидает список. Его можно было бы переместить за пределы цикла, и вам не понадобился бы оператор if.   -  person Lespied    schedule 20.04.2017
comment
Вероятно, вы можете использовать что-то вроде lapply(,'[[') для извлечения списка в первую очередь.   -  person Lespied    schedule 20.04.2017
comment
@Lespied, однако, я не думаю, что это работает с несколькими внутренними списками, верно? Я имею в виду, что я могу извлекать по одному с помощью lapply(j$response$docs, '[[', 'variable_long_name'), но не более одного за раз. Я пропустил простой способ сделать это?   -  person AF7    schedule 20.04.2017
comment
Проблема в том, что элементы списка имеют разное количество элементов (41 до 55-го, 42 после, затем 43, затем обратно до 41 и т. д.). Кроме того, что вы хотите делать с вложенными элементами, такими как «доступ» или «experiment_family»?   -  person GGamba    schedule 20.04.2017
comment
@GGamba посмотри принятый ответ, это именно то, что я искал. Отсутствующие элементы должны присутствовать и обозначаться как NA также для всех тех случаев, когда их нет в списке.   -  person AF7    schedule 21.04.2017
comment
Да, я видел это, голос мой, отличное решение   -  person GGamba    schedule 21.04.2017


Ответы (2)


Вы можете сделать это с помощью пакета purrr. Я думал, что здесь может работать at_depth, но вместо этого я использовал вложенный map_df.

library(purrr)

Ваши переменные имеют разную длину, поэтому первое, что нужно сделать, это убедиться, что каждая переменная имеет длину 1. Это можно сделать, свернув каждый элемент внутреннего списка с помощью paste. Я использовал запятые в качестве разделителя. Выполнение этого через map_df возвращает 1 строку tibble.

Вот пример с первым внутренним списком.

map_df(j$response$docs[[1]], paste, collapse = ",")

Теперь мы можем перебирать внешние списки, создавая по 1 строке tibble для каждого. Мы используем map_df, чтобы связать каждый из них вместе. На выходе получается 832 строки tibble, по одной строке на список. Я использовал аргумент .id, чтобы добавить к результату группирующую переменную, которая может и не понадобиться.

d1 = map_df(j$response$docs, ~map_df(.x, paste, collapse = ","))
d1

# A tibble: 832 × 45
   group                                                                                                   id  version
   <chr>                                                                                                <chr>    <chr>
1      1   cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.clh.v20131119|cordexesg.dmi.dk 20131119
2      2 cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.clivi.v20131119|cordexesg.dmi.dk 20131119
3      3  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rsds.v20131119|cordexesg.dmi.dk 20131119
4      4  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rlds.v20131119|cordexesg.dmi.dk 20131119
5      5  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rsus.v20131119|cordexesg.dmi.dk 20131119
6      6  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rlus.v20131119|cordexesg.dmi.dk 20131119
7      7  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rsdt.v20131119|cordexesg.dmi.dk 20131119
8      8  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rsut.v20131119|cordexesg.dmi.dk 20131119
9      9  cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.rlut.v20131119|cordexesg.dmi.dk 20131119
10    10   cordex.output.EUR-11.DMI.ICHEC-EC-EARTH.rcp85.r3i1p1.HIRHAM5.v1.day.psl.v20131119|cordexesg.dmi.dk 20131119
# ... with 822 more rows, and 42 more variables:

Если вы хотите получить несколько строк для переменных, длина которых превышает 1, например access и experiment_family, вы можете использовать tidyr::separate_rows для разделения данных на несколько строк.

tidyr::separate_rows(d1, experiment_family)
person aosmith    schedule 20.04.2017
comment
Это именно то, что я искал. purrr и dplyr такие блестящие, как только вы их освоите. - person AF7; 21.04.2017

вместо rjson используйте это:

library(jsonlite)
j <- jsonlite::fromJSON('https://esgf-data.dkrz.de/esg-search/search/?offset=0&limit=1000&type=Dataset&replica=false&latest=true&project=CORDEX&domain=EUR-11&experiment=rcp85&time_frequency=day&facets=rcm_name%2Cproject%2Cproduct%2Cdomain%2Cinstitute%2Cdriving_model%2Cexperiment%2Cexperiment_family%2Censemble%2Crcm_version%2Ctime_frequency%2Cvariable%2Cvariable_long_name%2Ccf_standard_name%2Cdata_node&format=application%2Fsolr%2Bjson')

# The names you wan to find in the nested returned data
look_for <- c('variable','variable_long_name' ,
              'rcm_name','driving_model',
              'cf_standard_name')


new_df <- as.data.frame(sapply(look_for, function(i){
  unlist(j$response$docs[[i]])
}))

str(new_df)
'data.frame':   832 obs. of  5 variables:
$ variable          : chr  "clh" "clivi" "rsds" "rlds" ...
$ variable_long_name: chr  "High Level Cloud Fraction" "Ice Water Path" "Surface Downwelling Shortwave Radiation" "Surface Downwelling Longwave Radiation" ...
$ rcm_name          : chr  "HIRHAM5" "HIRHAM5" "HIRHAM5" "HIRHAM5" ...
$ driving_model     : chr  "ICHEC-EC-EARTH" "ICHEC-EC-EARTH" "ICHEC-EC-EARTH" "ICHEC-EC-EARTH" ...
$ cf_standard_name  : chr  "cloud_area_fraction_in_atmosphere_layer" "atmosphere_cloud_ice_content" "surface_downwelling_shortwave_flux_in_air" "surface_downwelling_longwave_flux_in_air" ...
person Carl Boneri    schedule 20.04.2017