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?
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.
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)
}
Most helpful comment
I added an
st_coordinatesthat returns a matrix, somewhat similar toraster::geom()but with guaranteed havingXandYin column 1 and 2. Pls test and comment, also for @hadley .