0Pricing
R Academy · Lesson

Simple Features with the sf Package

Read, write, and manipulate spatial geometries using the sf standard.

Simple Features with the sf Package is a free R Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the R Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What Is the sf Package?

The sf (Simple Features) package is R's primary tool for geospatial vector data. It stores geometries (points, lines, polygons) as a special list column in a regular data frame, making it fully compatible with dplyr and ggplot2.

library(sf)

# sf data frames look like regular data frames
# but have a special 'geometry' column
cat('sf version:', packageVersion('sf'), '\n')

# Supported geometry types
cat('Geometry types: POINT, LINESTRING, POLYGON,\n')
cat('               MULTIPOINT, MULTILINESTRING, MULTIPOLYGON,\n')
cat('               GEOMETRYCOLLECTION\n')

read_sf: Loading Shapefiles

read_sf("file.shp") reads any OGR-supported format (Shapefile, GeoJSON, GeoPackage, KML, etc.) into an sf object. It automatically reads the CRS and attribute table alongside the geometries.

library(sf)

# Read a GeoJSON file from a URL
url <- 'https://raw.githubusercontent.com/datasets/geo-countries/master/data/countries.geojson'

# For demonstration, use the system example data
nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

cat('Class:', class(nc), '\n')
cat('Dimensions:', nrow(nc), 'rows x', ncol(nc), 'cols\n')
cat('CRS:', st_crs(nc)$epsg, '\n')
cat('Geometry type:', unique(st_geometry_type(nc)), '\n')
cat('Column names:', paste(names(nc)[1:5], collapse = ', '), '...\n')

st_geometry_type and Structure

st_geometry_type() returns the geometry type of each feature. The geometry column is a list column of sfc objects. You can inspect, subset, and manipulate it like any other column.

library(sf)
nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

# Geometry type for each feature
types <- st_geometry_type(nc)
cat('Unique types:', unique(as.character(types)), '\n')

# The geometry column
geom_col <- st_geometry(nc)
cat('Geometry class:', class(geom_col), '\n')
cat('First geometry:\n')
print(geom_col[[1]])  # MULTIPOLYGON coordinates

# Access as a normal column
nc$area_est <- st_area(nc)  # adds an area column
cat('\nArea column class:', class(nc$area_est), '\n')

st_coordinates: Extracting XY

st_coordinates() extracts raw coordinate matrices from any geometry. For points it returns a 2-column matrix (X, Y). For polygons it returns all ring vertices with part and polygon index columns.

library(sf)

# Create simple point features
pts <- st_sfc(
  st_point(c(-80.0, 35.0)),
  st_point(c(-79.5, 35.5)),
  st_point(c(-79.0, 34.8))
)
pts_sf <- st_sf(id = 1:3, geometry = pts)

# Extract coordinates
coords <- st_coordinates(pts_sf)
cat('Coordinates:\n')
print(coords)

# For polygons: includes part (L1) and polygon (L2) columns
nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))
ring_coords <- st_coordinates(nc[1, ])
cat('Polygon ring vertices:', nrow(ring_coords), '\n')
cat('Column names:', colnames(ring_coords), '\n')

Creating sf from Data Frame

st_as_sf(df, coords = c('lon', 'lat'), crs = 4326) converts a regular data frame with coordinate columns into an sf point object. The coords argument names the x (longitude) and y (latitude) columns.

library(sf)

# Typical scenario: CSV with lon/lat columns
cities <- data.frame(
  name = c('New York', 'Los Angeles', 'Chicago', 'Houston'),
  lon  = c(-74.006, -118.244, -87.629, -95.369),
  lat  = c(40.713,   34.052,   41.878,  29.760),
  pop  = c(8336817, 3979576, 2693976, 2304580)
)

# Convert to sf
cities_sf <- st_as_sf(
  cities,
  coords = c('lon', 'lat'),
  crs    = 4326
)

print(cities_sf)
cat('\nCRS:', st_crs(cities_sf)$epsg, '(WGS84)\n')

st_point, st_linestring, st_polygon

You can build geometries programmatically with constructor functions. st_point(c(x,y)) creates a point, st_linestring(matrix) creates a line from coordinate pairs, and st_polygon(list(matrix)) creates a polygon from ring coordinates.

library(sf)

# POINT
p <- st_point(c(-74.006, 40.713))
cat('Point:', class(p), '\n')

# LINESTRING: a route with 4 waypoints
route_coords <- matrix(
  c(-74.0, 40.7,
    -75.0, 41.0,
    -76.0, 41.5,
    -77.0, 42.0),
  ncol = 2, byrow = TRUE
)
line <- st_linestring(route_coords)
cat('Linestring length:', st_length(line), '\n')  # in degrees (no CRS yet)

# POLYGON: a simple bounding box
bbox_ring <- matrix(
  c(0,0, 1,0, 1,1, 0,1, 0,0),
  ncol = 2, byrow = TRUE
)
poly <- st_polygon(list(bbox_ring))
cat('Polygon area:', st_area(poly), '\n')

Plotting sf Objects

The plot() method for sf objects creates a quick map for each attribute column. Pass plot(st_geometry(sf_obj)) to plot just the geometry without attribute panels.

library(sf)
nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

# Plot all attributes (creates a panel per column)
# plot(nc)  # too many panels

# Plot a single attribute
plot(nc['BIR74'],
     main   = 'North Carolina: Births 1974',
     border = 'white',
     lwd    = 0.5)

# Plot geometry only
plot(st_geometry(nc),
     col    = 'lightblue',
     border = 'gray40',
     main   = 'NC Counties')

Subsetting sf Objects

sf objects behave like data frames: you can filter rows with [, subset(), or dplyr::filter(). The geometry column is automatically carried along in all row-subsetting operations.

library(sf)
library(dplyr)

nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

# Base R subsetting
western_nc <- nc[nc$CNTY_ID < 1830, ]
cat('Western counties:', nrow(western_nc), '\n')

# dplyr filter works identically
high_birth <- nc |>
  filter(BIR74 > 3000) |>
  select(NAME, BIR74, geometry)

cat('High-birth counties:', nrow(high_birth), '\n')
cat('Names:', paste(high_birth$NAME[1:3], collapse = ', '), '...\n')

sf and ggplot2 Integration

ggplot2 supports sf objects natively via geom_sf(). The function automatically handles the coordinate system and renders the correct geometry type — points, lines, or polygons — without any extra preparation.

library(sf)
library(ggplot2)

nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

# Choropleth map with ggplot2
ggplot(nc) +
  geom_sf(aes(fill = BIR74), color = 'white', lwd = 0.2) +
  scale_fill_viridis_c(
    option = 'plasma',
    name   = 'Births (1974)'
  ) +
  labs(
    title    = 'North Carolina Birth Counts, 1974',
    subtitle = 'County-level data'
  ) +
  theme_minimal()

Writing sf to Disk

write_sf(sf_obj, 'output.gpkg') (or st_write()) saves geometries and attributes to disk. GeoPackage (.gpkg) is preferred over Shapefile because it supports long column names, UTF-8, and stores everything in a single file.

library(sf)

nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

# Write to GeoPackage
out_path <- tempfile(fileext = '.gpkg')
write_sf(nc, out_path)

cat('Written to:', out_path, '\n')
cat('File size:', file.info(out_path)$size / 1024, 'KB\n')

# Round-trip: read back
nc_rt <- read_sf(out_path)
cat('Round-trip features:', nrow(nc_rt), '\n')
cat('Round-trip CRS match:', identical(st_crs(nc), st_crs(nc_rt)), '\n')

sf Summary and Inspection

Use print(), st_bbox(), st_crs(), and summary() to quickly inspect an sf object. st_bbox() returns the bounding box as a named vector (xmin, ymin, xmax, ymax) in the object's CRS units.

library(sf)
nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))

# Bounding box
bbox <- st_bbox(nc)
cat('Bounding box:\n')
cat('  xmin:', bbox['xmin'], ' xmax:', bbox['xmax'], '\n')
cat('  ymin:', bbox['ymin'], ' ymax:', bbox['ymax'], '\n')

# CRS
crs_info <- st_crs(nc)
cat('\nEPSG:', crs_info$epsg, '\n')
cat('Proj4:', crs_info$proj4string, '\n')

# Attribute summary
cat('\nBIR74 range:',
    range(nc$BIR74)[1], 'to',
    range(nc$BIR74)[2], '\n')

Quick Check

You have a data frame with columns longitude and latitude (WGS84). Which function call converts it to an sf point object?

Recap: sf Package

Key takeaways:

  • read_sf() loads shapefiles, GeoJSON, GeoPackage and other formats
  • st_geometry_type() returns POINT, LINESTRING, POLYGON, etc. per feature
  • st_coordinates() extracts raw XY coordinate matrices
  • st_as_sf(df, coords = c('x','y'), crs = 4326) creates sf from a data frame
  • Programmatic constructors: st_point(), st_linestring(), st_polygon()
  • plot(sf_obj) or geom_sf() in ggplot2 for visualisation
  • write_sf() saves to GeoPackage, Shapefile, GeoJSON, etc.
library(sf)

# Quick sf workflow
nc <- read_sf(system.file('shape/nc.shp', package = 'sf'))
cat('Features:', nrow(nc), '\n')
cat('CRS EPSG:', st_crs(nc)$epsg, '\n')
cat('Geom type:', unique(as.character(st_geometry_type(nc))), '\n')
cat('Bbox xmin:', st_bbox(nc)['xmin'], '\n')

Frequently asked questions

Is the “Simple Features with the sf Package” lesson free?

Yes — the full text of “Simple Features with the sf Package” is free to read here on the web, and the R Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the R Academy course, upgrade to CoddyKit PRO.

What will I learn in “Simple Features with the sf Package”?

Read, write, and manipulate spatial geometries using the sf standard. You practise R Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start R Academy?

No prior experience is required. R Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Simple Features with the sf Package” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this R Academy lesson?

Yes. Every R Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Simple Features with the sf Package
  2. Coordinate Reference Systems and Projections
  3. Spatial Joins and Operations
  4. Interactive Maps with leaflet
← Back to R Academy