3

I'm working with some spatial data and I want to turn the output of the st_nn command (outputs a list) into a data frame that I can join. For example

library(nngeo)
library(tidyverse)

cities <- cities
towns <- towns

nearest <- st_nn(towns, cities, returnDist = TRUE, k = 2)

The output of the list give me the unique ID of the two nearest cities and also the distances for those cities. I would like this as a tibble with the following columns: 1) nearest_id, (2) nearest_distance, (3)second_nearest_id, (4) second_nearest_distance.

Here is what I tired

tibble(col1 = unlist(nearest[[1]]), col2 = unlist(nearest[[2]])) %>% 
  rowid_to_column(., "row_id") # for joining later on

I need to find a way to keep the id/distances for the two nearest neighbors separate and give the id/distances each its own column. I tried a st_join with st_nn but that doesn't return distance.

2 Answers 2

1

Make a data frame by combining two matrices constructed from the unlisted parts of the list:

> data.frame(
      cbind(
            matrix(
                   unlist(nearest[[1]]),
                   ncol=2,
                   byrow=TRUE), 
            matrix(
                   unlist(nearest[[2]]),
                   ncol=2,
                   byrow=TRUE)
            ) 
      )
  X1 X2       X3        X4
1  3  2 53071.50  99627.77
2  1  2 15069.49  57746.32
3  3  2 33345.18 113133.89
4  3  2 46392.06 104263.13
5  3  2 57459.81  98846.89
> 

Or use do.call to row-bind each part of nearest and then convert to data frame:

> data.frame(do.call(rbind,nearest[[1]]),do.call(rbind,nearest[[2]]))
  X1 X2     X1.1      X2.1
1  3  2 53071.50  99627.77
2  1  2 15069.49  57746.32
3  3  2 33345.18 113133.89
4  3  2 46392.06 104263.13
5  3  2 57459.81  98846.89

Name the columns and re-order them in whatever way you'd normally do that.

Above is tested with the first five towns only so I can see everything in one screen and check the nearest list conforms with the data frame. Its easy to get things the wrong way round or fill the matrix in the wrong way.

> nearest
[[1]]
[[1]][[1]]
[1] 3 2

[[1]][[2]]
[1] 1 2

[[1]][[3]]
[1] 3 2

[[1]][[4]]
[1] 3 2

[[1]][[5]]
[1] 3 2


[[2]]
[[2]][[1]]
[1] 53071.50 99627.77

[[2]][[2]]
[1] 15069.49 57746.32

Looks good.

0

The function below will:

  • handle cases when there are different number of neighbors per id (if maxdist is given)
  • return the row_number of the x object
  • return the format in a long format

This solves issues with the functions provided by @Spacedman, which would fail when maxdist is used.

library(nngeo)
#> Loading required package: sf
#> Linking to GEOS 3.10.1, GDAL 3.4.0, PROJ 8.2.0; sf_use_s2() is TRUE
library(tidyverse)

nearest <- st_nn(towns[1:2,], cities, returnDist = TRUE, k = 2, maxdist = 40000)
#> lon-lat points
#>   |                                                                              |                                                                      |   0%  |                                                                              |===================================                                   |  50%  |                                                                              |======================================================================| 100%

nn_to_df <- function(nn_out) {
  out <- map(names(nn_out), ~enframe(pluck(nn_out, .), name = "row_id", value = .) %>% 
        unnest(cols=all_of(.x))) 
    bind_cols(out[[1]], out[[2]] %>% select(-row_id))
}

nn_to_df(nn_out=nearest)
#> # A tibble: 2 × 3
#>   row_id    nn   dist
#>    <int> <int>  <dbl>
#> 1      1     3 34147.
#> 2      2     3 18266.

## other functions fail here
data.frame(
  cbind(
    matrix(
      unlist(nearest[[1]]),
      ncol=2,
      byrow=TRUE), 
    matrix(
      unlist(nearest[[2]]),
      ncol=2,
      byrow=TRUE)))
#>   X1 X2       X3       X4
#> 1  3  3 34146.94 18265.72

data.frame(do.call(rbind,nearest[[1]]),do.call(rbind,nearest[[2]]))
#>   do.call.rbind..nearest..1... do.call.rbind..nearest..2...
#> 1                            3                     34146.94
#> 2                            3                     18265.72

Created on 2022-03-23 by the reprex package (v2.0.1)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.