0

This is my dataframe:

df <- data.frame(ID = rep(1:100, each = 2), x =runif(200, 10, 90), y = runif(200, 10, 90))

For each ID I need to create SpatialLines, I haven't had good luck.

I think I have to split the dataframe and then transform to Lines class and after that the SpatialLines using the splited dataframe. All that using inside of a apply function. Am I right? How would you do it?

  • 1
    See spbabel::sp for a way,will explain later – mdsumner Nov 17 '16 at 21:16
4

I am sure that one could write a function that would do this using lapply but I am not sure that it is worth the trouble. In this case, I am not sure that an apply like approach would buy you much.

A simple approach is a for loop to build the lines by ID and store in a list object. Then you can use do.call to combine the results into a single SpatialLines object. The confusion with SpatialLines is that they have to be created using a list of "Line" object(s) and then a list object containing "Lines" object(s), even if a single line.

library(sp)
df <- data.frame(ID = rep(1:100, each = 2), x =runif(200, 10, 90), 
                 y = runif(200, 10, 90))

lines.list <- list()                 
  for( i in unique(df$ID) ) {
    l <- list(as.matrix(df[df$ID == i,][2:3]))
    lines.list[[i]] <- SpatialLines(list(Lines(list(Line(l)), ID=as.character(i))))
  }                  
my.lines <- do.call("rbind", lines.list)

plot(my.lines)

If this is something that you want to export to a GIS (eg., shapefile format), the data needs to be in a SpatialLinesDataFrame format. A SpatialLines object can be coerced to a suitable object quite easily using the SpatialLinesDataFrame function along with data.frame to define the corresponding @data slot object.

my.lines <- SpatialLinesDataFrame(my.lines, 
    data.frame(row.names = as.character(unique(df$ID)), 
    ID = 1:length(my.lines) ) )
  • It works, I've tried in the real data and says "NA values in coordinates", in my data frame I don't have NAs, do you suspect what could it be? – Ariel Nov 17 '16 at 20:09
  • 1
    I imagine that an NA error could be returned because the start and end coordinates are either the same or one is missing. This code assumes that you have two ID's representing the start and end points of each line. – Jeffrey Evans Nov 17 '16 at 20:21
3

The spbabel package has functions for this.

set.seed(71)
df <- data.frame(ID = rep(1:100, each = 2), x =runif(200, 10, 90), y = runif(200, 10, 90))


library(spbabel)
library(dplyr)
single_segment <- 
df %>% mutate(object_ = ID, branch_ = ID, order_ = row_number()) %>% 
rename(x_ = x, y_ = y) %>% 
sp()

 ## each pair is an entire object per row
 plot(single_segment, col = viridis::viridis(nrow(single_segment)))

Or you might want just a single object with multiple parts.

single_object <- 
 df %>% mutate(object_ = 1, branch_ = ID, order_ = row_number()) %>% 
 rename(x_ = x, y_ = y) %>% 
 sp()

## we can't give more than one colour (or width, etc.)
plot(single_object, col = "firebrick", lwd = 2)

nrow(single_object)

A future version of this will remove the need to make the specific names object_, branch_, order_, x_, y_, etc. and use a gg-like syntax, because it's just group-by, arrange and so on.

Note that you can already plot this in ggplot2 as is, though you probably should use geom_segment (or something).

 library(ggplot2)
 ggplot(df) + aes(x = x, y = y, group = ID, color = factor(ID)) + geom_path() + guides(color = FALSE)
  • Well that is very helpful to know @mdsumner :-) Is it possible to have sp() return a 'SpatialLines' object, i.e. without any data attached? That would render an explicit call to as(single_segment, "SpatialLines") redundant if you wished not to work with 'Spatial*DataFrame' objects. – fdetsch Nov 21 '16 at 9:03
  • No, this for me is all about dataframes, but I'm not opposed in principle, it's just another function. – mdsumner Nov 21 '16 at 10:09
2

A while ago, we put the manual approach elaborated by @JeffreyEvans into two functions - one for 'SpatialLines*' (coords2Lines) and one for 'SpatialPolygons*' (coords2Polygons) - included in the mapview package. Here's how the code would look like in your particular case.

library(foreach)
library(mapview)
library(sp)

## split by id
lst <- split(df, df$ID)

## create 'SpatialLines' obects
sln <- foreach(i = lst, id = names(lst), .combine = rbind) %do% {
  coords2Lines(as.matrix(i[, 2:3]), ID = id)
}

And here's the resulting object.

> sln
class       : SpatialLines 
features    : 100 
extent      : 10.07773, 89.92601, 10.60766, 89.20453  (xmin, xmax, ymin, ymax)
coord. ref. : NA

Note that the mapview functions also support the specification of other arguments to be passed to the underlying Spatial* functions, e.g. 'proj4string'.

  • Thank you for adding your answer. Nice, efficient solution. When I am teaching analysis using R I try to impart that there are multiple ways to approach and solve a given problem. This thread is the perfect example of different approaches (some, much more efficient) getting to the same result. – Jeffrey Evans Nov 23 '16 at 16:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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