Sf: Convert sfc_POINT to x,y columns

Created on 24 Feb 2017  路  12Comments  路  Source: r-spatial/sf

Hello

I'm slowly working through various analyses and trying to convert from an sp-centric approach to an sf-centric approach. For the most part, so far so good.

One step I can't quite figure out how to replicate converting an sf data.frame with an sfc_POINT geometry column into a data.frame with separate x and y columns.

Here's how I would have done it via sp

library(tidyverse)
library(argosfilter) #needed for the example seal data
library(sp)

data(seal)

seal_sp <- seal
coordinates(seal_sp) <- ~ lon + lat
proj4string(seal_sp) <- CRS("+init=epsg:4326")
head(seal_sp)
as.data.frame(seal_sp) %>% head()

and here's my best attempt via sf

library(tidyverse)
library(argosfilter)
library(sf)

data(seal)

seal_sf <- st_as_sf(seal, coords = c("lon","lat")) %>% 
  st_set_crs(4326)

seal_coords <- unlist(st_geometry(seal_sf)) %>% 
  matrix(ncol=2,byrow=TRUE) %>% 
  as_tibble() %>% 
  setNames(c("lon","lat"))

bind_cols(seal_sf,seal_coords) %>% head()

Is there a better (simpler) approach I'm not seeing?

Most helpful comment

I added an st_coordinates that returns a matrix, somewhat similar to raster::geom() but with guaranteed having X and Y in column 1 and 2. Pls test and comment, also for @hadley .

All 12 comments

I would do

seal_coords <- do.call(rbind, st_geometry(seal_sf)) %>% 
    as_tibble() %>% setNames(c("lon","lat"))

which is only slightly less low-level. It has the advantage that it will also work for XYZ, XYM or XYZM geometries; your approach with ncol=2 assumes XY.

Thanks, edzer.

To facilitate piping and make things a bit easier I've got the following function. Included here in case others need similar functionality. if you think it's worth including within the package, I'd be happy to submit a pull request.

sfc_as_cols <- function(x, names = c("x","y")) {
  stopifnot(inherits(x,"sf") && inherits(sf::st_geometry(x),"sfc_POINT"))
  ret <- do.call(rbind,sf::st_geometry(x))
  ret <- tibble::as_tibble(ret)
  stopifnot(length(names) == ncol(ret))
  ret <- setNames(ret,names)
  dplyr::bind_cols(x,ret)
}

Follow-up from Leeds: the function suggested by @jmlondon would be really useful for us (or st_coords or st_coordinates to match sp funs). Is this being implemented? @virgesmith just want to extract the coords and I suspect many more people will to.

  • would be great if this generalised to pull out all vertices in lines and polys, a bit like raster::geom() which I've found to be a very useful function.

I can't get the code snippets above to work on my data, but - not ideal I know - converting it to sp seems to do the trick:

> oRand
Geometry set for 1 feature 
geometry type:  POINT
dimension:      XY
bbox:           xmin: -1.659279 ymin: 54.97218 xmax: -1.659279 ymax: 54.97218
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
POINT(-1.65927923899647 54.9721762575333)

> class(oRand)
[1] "sfc_POINT" "sfc"   

> as.vector(as(oRand, "Spatial")@coords)
[1] -1.659279 54.972176

> class(as.vector(as(oRand, "Spatial")@coords))
[1] "numeric"

@virgesmith re-reading the above, I guess

st_geometry(oRand)

might provide the result you're after.

Not sure that actually does anything, type appears to be unchanged:

> class(oRand)
[1] "sfc_POINT" "sfc"      
> class(st_geometry(oRand))
[1] "sfc_POINT" "sfc"      

try

unclass(oRand[[1]])

I added an st_coordinates that returns a matrix, somewhat similar to raster::geom() but with guaranteed having X and Y in column 1 and 2. Pls test and comment, also for @hadley .

Fantastic - look forward to giving it a spin and maybe adding some documentation.

I've been testing the use of st_coordinates a bit and all seems to work as expected.

Here's an updated version of the sfc_as_cols function I provided earlier:

sfc_as_cols <- function(x, names = c("x","y")) {
  stopifnot(inherits(x,"sf") && inherits(sf::st_geometry(x),"sfc_POINT"))
  ret <- sf::st_coordinates(x)
  ret <- tibble::as_tibble(ret)
  stopifnot(length(names) == ncol(ret))
  x <- x[ , !names(x) %in% names]
  ret <- setNames(ret,names)
  dplyr::bind_cols(x,ret)
}

A small improvement to take optimal geometry column so it works on sf that is not point based but compute a point based on column on the fly such as

sfc_as_cols(x, st_centroid(geometry))
sfc_as_cols <- function(x, geometry, names = c("x","y")) {
  if (missing(geometry)) {
    geometry <- sf::st_geometry(x)
  } else {
    geometry <- rlang::eval_tidy(enquo(geometry), x)
  }
  stopifnot(inherits(x,"sf") && inherits(geometry,"sfc_POINT"))
  ret <- sf::st_coordinates(geometry)
  ret <- tibble::as_tibble(ret)
  stopifnot(length(names) == ncol(ret))
  x <- x[ , !names(x) %in% names]
  ret <- setNames(ret,names)
  dplyr::bind_cols(x,ret)
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

kendonB picture kendonB  路  4Comments

kendonB picture kendonB  路  3Comments

faridcher picture faridcher  路  4Comments

jsta picture jsta  路  4Comments

kendonB picture kendonB  路  3Comments