R Сглаживание вложенных списков разной длины (вывод API геокода Google) в R - PullRequest
6 голосов
/ 27 мая 2020

Я использовал API геокодирования от Google для геокодирования списков адресов. Он возвращает результаты во вложенных списках. Элементы в списках могут различаться, а иногда встречаются частичные совпадения, что приводит к множеству вложенных списков, вложенных на самом высоком уровне. До сих пор я сохранял каждый результат GoogleResult в одной ячейке фрейма данных.

Вот пример моего фрейма данных:

    df <- structure(list(address = structure(c(3L, 1L, 2L), .Label = c("115 Civic Parade, Altona VIC 3018", 
"Civic Parade, Altona VIC 3018", "EAST LA CLARKEFIELD 3430"), class = "factor"), 
    GoogleResult = list(list(list(access_points = list(), address_components = list(
        list(long_name = "Los Angeles", short_name = "Los Angeles", 
            types = list("locality", "political")), list(long_name = "Los Angeles County", 
            short_name = "Los Angeles County", types = list("administrative_area_level_2", 
                "political")), list(long_name = "California", 
            short_name = "CA", types = list("administrative_area_level_1", 
                "political")), list(long_name = "United States", 
            short_name = "US", types = list("country", "political"))), 
        formatted_address = "Los Angeles, CA, USA", geometry = list(
            bounds = list(northeast = list(lat = 34.3373061, 
                lng = -118.1552891), southwest = list(lat = 33.7036519, 
                lng = -118.6681759)), location = list(lat = 34.0522342, 
                lng = -118.2436849), location_type = "APPROXIMATE", 
            viewport = list(northeast = list(lat = 34.3373061, 
                lng = -118.1552891), southwest = list(lat = 33.7036519, 
                lng = -118.6681759))), partial_match = TRUE, 
        place_id = "ChIJE9on3F3HwoAR9AhGJW_fL-I", types = list(
            "locality", "political")), list(access_points = list(), 
        address_components = list(list(long_name = "3430", short_name = "3430", 
            types = list("postal_code")), list(long_name = "Clarkefield", 
            short_name = "Clarkefield", types = list("locality", 
                "political")), list(long_name = "Victoria", short_name = "VIC", 
            types = list("administrative_area_level_1", "political")), 
            list(long_name = "Australia", short_name = "AU", 
                types = list("country", "political"))), formatted_address = "Clarkefield VIC 3430, Australia", 
        geometry = list(bounds = list(northeast = list(lat = -37.4364578, 
            lng = 144.8986988), southwest = list(lat = -37.5280439, 
            lng = 144.7012193)), location = list(lat = -37.497542, 
            lng = 144.8071366), location_type = "APPROXIMATE", 
            viewport = list(northeast = list(lat = -37.4364578, 
                lng = 144.8986988), southwest = list(lat = -37.5280439, 
                lng = 144.7012193))), partial_match = TRUE, place_id = "ChIJS3IdP-xX1moRkD8uRnhWBBw", 
        types = list("postal_code"))), list(list(access_points = list(), 
        address_components = list(list(long_name = "115", short_name = "115", 
            types = list("street_number")), list(long_name = "Civic Parade", 
            short_name = "Civic Parade", types = list("route")), 
            list(long_name = "Altona", short_name = "Altona", 
                types = list("locality", "political")), list(
                long_name = "Hobsons Bay City", short_name = "Hobsons Bay", 
                types = list("administrative_area_level_2", "political")), 
            list(long_name = "Victoria", short_name = "VIC", 
                types = list("administrative_area_level_1", "political")), 
            list(long_name = "Australia", short_name = "AU", 
                types = list("country", "political")), list(long_name = "3018", 
                short_name = "3018", types = list("postal_code"))), 
        formatted_address = "115 Civic Parade, Altona VIC 3018, Australia", 
        geometry = list(bounds = list(northeast = list(lat = -37.8633208, 
            lng = 144.8316509), southwest = list(lat = -37.86409, 
            lng = 144.8303929)), location = list(lat = -37.863727, 
            lng = 144.8310159), location_type = "ROOFTOP", viewport = list(
            northeast = list(lat = -37.8623564197085, lng = 144.832370880292), 
            southwest = list(lat = -37.8650543802915, lng = 144.829672919709))), 
        place_id = "ChIJBXz75NRj1moRpVRt21nooQw", types = list(
            "premise"))), list(list(access_points = list(), address_components = list(
        list(long_name = "Civic Parade", short_name = "Civic Parade", 
            types = list("route")), list(long_name = "Altona", 
            short_name = "Altona", types = list("locality", "political")), 
        list(long_name = "Hobsons Bay City", short_name = "Hobsons Bay", 
            types = list("administrative_area_level_2", "political")), 
        list(long_name = "Victoria", short_name = "VIC", types = list(
            "administrative_area_level_1", "political")), list(
            long_name = "Australia", short_name = "AU", types = list(
                "country", "political")), list(long_name = "3018", 
            short_name = "3018", types = list("postal_code"))), 
        formatted_address = "Civic Parade, Altona VIC 3018, Australia", 
        geometry = list(bounds = list(northeast = list(lat = -37.8626502, 
            lng = 144.8449271), southwest = list(lat = -37.8661171, 
            lng = 144.81081)), location = list(lat = -37.864412, 
            lng = 144.8303004), location_type = "GEOMETRIC_CENTER", 
            viewport = list(northeast = list(lat = -37.8626502, 
                lng = 144.8449271), southwest = list(lat = -37.8661171, 
                lng = 144.81081))), place_id = "EihDaXZpYyBQYXJhZGUsIEFsdG9uYSBWSUMgMzAxOCwgQXVzdHJhbGlhIi4qLAoUChIJtbGXUCti1moRKcxHhdx2QrYSFAoSCSEyccGdYdZqEXDajCF1VgQF", 
        types = list("route"))))), row.names = c(NA, -3L), class = "data.frame")

Первый случай имеет частичное совпадение, это два вложенных списка результатов.

Мой ожидаемый результат:

  • фрейм данных со всеми элементами всех списков в виде столбцов
  • все столбцы должны быть названы соответствующим образом
  • Частичные совпадения имеют> 1 результат, который может быть либо 1 строкой на совпадение, либо просто расширять фрейм данных с помощью переменных 'address2'. В любом случае я могу работать с.

Я пробовал такие вещи, как:

lapply(df$GoogleResult, data.frame, stringsAsFactors = FALSE)

, но элементы различаются по длине ... в результате:

arguments imply differing number of rows: 0, 1

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

Любая помощь приветствуется!

Ура,

Лю c

Ответы [ 3 ]

1 голос
/ 27 мая 2020

можешь попробовать что-нибудь вроде:

df %>% 
 unnest(col = GoogleResult) %>% unnest(col = GoogleResult)%>%
  filter(lengths(GoogleResult)>0)%>%
  {map2(.$GoogleResult,.$address,
        ~cbind(address = .y,data.frame(fromJSON(toJSON(.x))))%>%unnest())}%>%
  plyr::rbind.fill()
0 голосов
/ 07 июня 2020
df <- df %>% unnest(col = GoogleResult)
GoogleResult <- as.list(df2$GoogleResult)
GoogleResult <- lapply(GoogleResult, function(i) as.list(unlist(i, recursive = FALSE)))
GoogleResult <- plyr::rbind.fill(lapply(GoogleResult, as.data.frame))
df <- cbind(address = df$address, GoogleResult)

Это возвращает фрейм данных со следующими свойствами (в соответствии с указанными критериями). Однако это не похоже на очень чистые данные.

  • фрейм данных со всеми элементами всех списков в виде столбцов
  • все столбцы названы в соответствии с элементом списка значение происходит от
  • 1 строка на совпадение для частичных совпадений
0 голосов
/ 05 июня 2020

Я не могу понять, как вы получили объект df в этой форме, основываясь на данных, возвращаемых функцией ggmap::geocode(). Вместо этого я попытался просто сгладить точный возвращаемый объект из запущенного ggmap::geocode() из примера документации, но обязательно использовать source="google" и output="all".

Функция ниже не будет принимать векторы, но я думаю, вы сможете легко отредактировать его. В частности, geo_mat <- matrix(geo2, nrow=1, byrow=T) - это строка кода, которая делает это не очень "дружелюбным к векторам". Это просто метод, который я выбрал для того, чтобы data.frame был широким, а не длинным.

Я подозреваю, что вы не очень беспокоитесь об эффективности, поскольку API Google в любом случае ограничен по скорости, поэтому эта функция может просто лучше всего работает в al oop или что-то в этом роде.

Также стоит отметить, что в объекте списка, возвращаемом функцией ggmap::geocode(), есть два именованных компонента. Один - results, другой - status. Компонент status не будет вам очень интересен, если в процессе геокодирования не возникнет какой-либо ошибки. В противном случае это будет просто символ 'Ok'. Приведенная ниже функция никоим образом не использует часть status объекта списка.

Я определенно понимаю, что вы говорите о «выстраивании» различных имен столбцов на основе переменного количества элементов в возвращаемом JSON. Я очень часто использую API карт Google, и моя стратегия для этого состоит в том, чтобы получить хороший образец адресов, которые я геокодирую, затем получить представление о том, какие поля я хочу захватить, а затем написать функцию для конкретной охоты вниз по этим полям, возвращая NA, если они не существуют.

В любом случае, вот моя попытка сгладить единственное возвращаемое значение из функции geocode.

library(jsonlite)
library(purrr)
library(ggmap)
register_google(key=key)  # <-- I stored my key in a variable called key...

flatten_geocode <- function(geocode_output) {
    #' geocode_output: output of ggmap::geocode()
    #' tested only when source="google" and output="all"


    # isolate the results
    geo1      <- purrr::flatten(geocode_output$results)

    # unlist (this will retain the flattened names to be used later)
    geo2      <- unlist(geo1)

    # convert to wide-matrix, then dataframe
    geo_mat   <- matrix(geo2, nrow=1, byrow=T)  
    geo_df    <- data.frame(geo_mat, stringsAsFactors=F)

    # clean the column names up (I hate periods in R data.frame names)
    # the second "data.frame()" call is to use "check.names" to remove 
    # duplicate column names
    names(geo_df) <- names(geo2)
    geo_df    <- data.frame(geo_df, stringsAsFactors = F, check.names = T)
    names(geo_df) <- gsub("\\.", "_", tolower(names(geo_df)))

    return(geo_df)
}



this_geocode_output <- ggmap::geocode("1600 pennsylvania avenue, washington dc",
           source="google", output="all")

df_output <- flatten_geocode(this_geocode_output)
df_output
...