# Creating lines between points with the same ID and calculating centroid in R

I have the following dataframe with an ID column and XY point coordinates. It's a simple case with only two points per line (dataframe only has ID pairs):

``````data <- data.frame(
id = c("1C1", "1C1", "1C2", "1C2"),
X = c(666427.3830, 666440.9899, 666375.8780, 666365.8079),
Y = c(4681239.659, 4681239.3219,4680517.0930, 4680526.1789))
``````

Each ID is duplicated because it points to the start and end of a line. I have created an R script to perform the following steps with the `sf` package:

1. Filter the dataframe by ID
2. Create a `LINESTRING` geometry object
3. Find each centroid
4. Store the ID and its centroid point in a new dataframe
``````library(sf)
points <- st_as_sf(data, coords = c('X', 'Y'), crs = 25830)

# Get unique IDs
ids <- levels(as.factor(points\$id))
# Apply a function to get points from the above IDs and
# create a LINESTRING object
makeLine <- function(id){
# Select two points (start and end) by ID
ps <- points[points\$id == id,]
# Create a Linestring geometry object
geo <- st_cast(st_union(ps[1, 'geometry'], ps[2, 'geometry']), "LINESTRING")
return(geo)
}
# Get lines by points
lines <- lapply(ids, makeLine)
# Unlist values to extract the sf geometry objects
lines_ <- do.call(c, unlist(lines, recursive=FALSE))
# Save centroids
centroids <- st_centroid(lines_)
# Create a new sf object with centroids and their IDs
points_c <- st_as_sf(data.frame(ids = ids, centroids))
plot(points_c)
``````

Is there a better way to achieve the above result in R?

I know that in this GIS StackExchange post and in this one there are approaches to do these operations, but I want to perform the analysis in R. This post to create a line by 2 geometry columns in the same dataframe gave me some inspiration.

``````R version 4.3.3 (2024-02-29 ucrt)
sf version 1.0-16
``````
• Do you only ever have two points in each line? Or can lines have more than two points (ie duplicated ID values)? What about one point? Can that happen? Commented May 6 at 11:25
• I have edited the question to clarify your point. Thanks for the comment! Commented May 6 at 11:37

I don't know if this a "better" way, but thanks to the integration of `sf` and `dplyr` you can also do a `group_by(id)` + `summarise()` (that is similar to perform a `st_union` by `level`). The code would be simplified as:

``````data <- data.frame(
id = c("1C1", "1C1", "1C2", "1C2"),
X = c(666427.3830, 666440.9899, 666375.8780, 666365.8079),
Y = c(4681239.659, 4681239.3219,4680517.0930, 4680526.1789))

library(sf)
library(dplyr)

points <- st_as_sf(data, coords = c('X', 'Y'), crs = 25830)

# Make lines
lines <- points %>%
group_by(id) %>%
summarise() %>%
st_cast("LINESTRING")

lines
#> Simple feature collection with 2 features and 1 field
#> Geometry type: LINESTRING
#> Dimension:     XY
#> Bounding box:  xmin: 666365.8 ymin: 4680517 xmax: 666441 ymax: 4681240
#> Projected CRS: ETRS89 / UTM zone 30N
#> # A tibble: 2 x 2
#>   id                                geometry
#>   <chr>                     <LINESTRING [m]>
#> 1 1C1     (666427.4 4681240, 666441 4681239)
#> 2 1C2   (666365.8 4680526, 666375.9 4680517)

# And just the centroids
centroids <- st_centroid(lines)
#> Warning in st_centroid.sf(lines): st_centroid assumes attributes are constant
#> over geometries of x

centroids
#> Simple feature collection with 2 features and 1 field
#> Geometry type: POINT
#> Dimension:     XY
#> Bounding box:  xmin: 666370.8 ymin: 4680522 xmax: 666434.2 ymax: 4681239
#> Projected CRS: ETRS89 / UTM zone 30N
#> # A tibble: 2 x 2
#>   id              geometry
#> * <chr>        <POINT [m]>
#> 1 1C1   (666434.2 4681239)
#> 2 1C2   (666370.8 4680522)
``````

Created on 2024-05-06 with reprex v2.1.0

• Thank you for your comment. I compared your approach with mine by applying the codes to a CSV with 180 points. My version takes 1.22 seconds, while yours takes 0.8 seconds, so I assume it's better in terms of performance. I have one more question: why does a warning message appear when your `st_centroid` is applied? I don't get that warning. Commented May 6 at 12:05
• I think that's because your approach creates `sfc` objects (i.e., just the geometry without extra features) while mine creates a `tibble/sf` object preserving the "feature" `id`. If you do `st_geometry(lines) |> st_centroid()` you won't see the warning, that can be in any case ignored safely. Commented May 6 at 16:11