Hannah Owens
05 March, 2024
Agenda
Agenda
Intended learning outcomes
“a framework for gathering, managing, and analyzing spatial data”
GIS | R |
|---|---|
No programming knowledge needed | Repeatable and scaleable |
Easy to create new spatial objects | Free and open-source |


Vector | Raster |
|---|---|
High geographical accuracy | Very good for representing remote sensing data |
Efficient representation of hierarchical data | Performs well with map algebra |
Used for measurements of proximity | Works well with continuous data |



Sherman's Fox Squirrels
![]()

If you haven't done so already:
install.packages("terra")

library(terra)
citation("terra")
To cite package 'terra' in publications use:
Hijmans R (2024). _terra: Spatial Data Analysis_. R package version
1.7-71, <https://CRAN.R-project.org/package=terra>.
A BibTeX entry for LaTeX users is
@Manual{,
title = {terra: Spatial Data Analysis},
author = {Robert J. Hijmans},
year = {2024},
note = {R package version 1.7-71},
url = {https://CRAN.R-project.org/package=terra},
}
Key function: vect()
vect()
class : SpatVector
geometry : none
dimensions : 0, 0 (geometries, attributes)
extent : 0, 0, 0, 0 (xmin, xmax, ymin, ymax)
coord. ref. :
From .csv file
#Read points and turn into shapefile
shermanSquirrels <- read.csv(file = "data/ShermanSquirrels.csv", header = T)
class(shermanSquirrels)
[1] "data.frame"
head(shermanSquirrels, 3)
Species Longitude Latitude
1 Sciurus niger -83.36905 31.40090
2 Sciurus niger -82.60067 28.46997
3 Sciurus niger -81.46268 28.71139
Converting a data.frame to a SpatVector
shermanSquirrelsShp <- vect(shermanSquirrels, geom = c("Longitude", "Latitude"))
shermanSquirrelsShp
class : SpatVector
geometry : points
dimensions : 35, 1 (geometries, attributes)
extent : -87.04154, -80.0361, 26.7061, 34.82 (xmin, xmax, ymin, ymax)
coord. ref. :
names : Species
type : <chr>
values : Sciurus niger
Sciurus niger
Sciurus niger
Polygons from .shp files
# Reading shapefiles
florida <- vect("data/FloridaBound1/FLORIDA.shp")
florida
class : SpatVector
geometry : polygons
dimensions : 1, 4 (geometries, attributes)
extent : -1110383, 1298633, 77020.92, 2466420 (xmin, xmax, ymin, ymax)
source : FLORIDA.shp
coord. ref. : NAD83(HARN) / Florida West (ftUS)
names : OBJECTID DATESTAMP SHAPE_AREA SHAPE_LEN
type : <int> <chr> <num> <num>
values : 1 2000/05/16 1.57e+12 2.834e+07
Key function: rast()
rast()
class : SpatRaster
dimensions : 180, 360, 1 (nrow, ncol, nlyr)
resolution : 1, 1 (x, y)
extent : -180, 180, -90, 90 (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84
Raster from .tif files
# Read rasters
altitude <- rast("data/altitude.tif")
altitude
class : SpatRaster
dimensions : 3000, 3000, 1 (nrow, ncol, nlyr)
resolution : 0.01666667, 0.01666667 (x, y)
extent : -100, -50, 0, 50 (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84 (EPSG:4326)
source : altitude.tif
name : ETOPO_2022_v1_60s_N90W180_bed
min value : -8461.562
max value : 5424.277
names(altitude)
[1] "ETOPO_2022_v1_60s_N90W180_bed"
Now it's your turn! Load the point .csv, shapefile, and raster.
# Points from .csv
shermanSquirrels <- read.csv(file = "data/ShermanSquirrels.csv",
header = T)
shermanSquirrelsShp <- vect(shermanSquirrels,
geom = c("Longitude",
"Latitude"))
paste0("There are ",
length(shermanSquirrelsShp),
" squirrels in the dataset.")
[1] "There are 35 squirrels in the dataset."
# Polygon shapefile
florida <- vect("data/FloridaBound1/FLORIDA.shp")
# Raster
altitude <- rast("data/altitude.tif")
Lots of generic methods work for terra data classes
print(shermanSquirrelsShp)
class : SpatVector
geometry : points
dimensions : 35, 1 (geometries, attributes)
extent : -87.04154, -80.0361, 26.7061, 34.82 (xmin, xmax, ymin, ymax)
coord. ref. :
names : Species
type : <chr>
values : Sciurus niger
Sciurus niger
Sciurus niger
summary(shermanSquirrelsShp)
Species
Length:35
Class :character
Mode :character
Lots of generic methods work for terra data classes
print(florida)
class : SpatVector
geometry : polygons
dimensions : 1, 4 (geometries, attributes)
extent : -1110383, 1298633, 77020.92, 2466420 (xmin, xmax, ymin, ymax)
source : FLORIDA.shp
coord. ref. : NAD83(HARN) / Florida West (ftUS)
names : OBJECTID DATESTAMP SHAPE_AREA SHAPE_LEN
type : <int> <chr> <num> <num>
values : 1 2000/05/16 1.57e+12 2.834e+07
Lots of generic methods work for terra data classes
summary (florida)
OBJECTID DATESTAMP SHAPE_AREA SHAPE_LEN
Min. :1 Length:1 Min. :1.57e+12 Min. :28344148
1st Qu.:1 Class :character 1st Qu.:1.57e+12 1st Qu.:28344148
Median :1 Mode :character Median :1.57e+12 Median :28344148
Mean :1 Mean :1.57e+12 Mean :28344148
3rd Qu.:1 3rd Qu.:1.57e+12 3rd Qu.:28344148
Max. :1 Max. :1.57e+12 Max. :28344148
Lots of generic methods work for terra data classes
print (altitude)
class : SpatRaster
dimensions : 3000, 3000, 1 (nrow, ncol, nlyr)
resolution : 0.01666667, 0.01666667 (x, y)
extent : -100, -50, 0, 50 (xmin, xmax, ymin, ymax)
coord. ref. : lon/lat WGS 84 (EPSG:4326)
source : altitude.tif
name : ETOPO_2022_v1_60s_N90W180_bed
min value : -8461.562
max value : 5424.277
Lots of generic methods work for terra data classes
summary(altitude)
ETOPO_2022_v1_60s_N90W180_bed
Min. :-8391.2
1st Qu.:-4335.3
Median : -793.8
Mean :-1925.9
3rd Qu.: 141.7
Max. : 4519.3
Plotting looks very familiar
# Let's try plotting
plot(altitude, main="Altitude", xlim = c(-88.5,-79), ylim = c(24, 32))
plot(shermanSquirrelsShp, pch = 16, cex = 1.5, add = T)
What if we add the Florida shapefile?
# Let's try plotting
plot(altitude, main="Altitude", xlim = c(-88.5,-79), ylim = c(24, 32))
plot(shermanSquirrelsShp, pch = 16, cex = 1.5, add = T)
plot(florida, add = T)
Let's investigate
ext() gives you the spatial extent of a file
ext(shermanSquirrelsShp)
SpatExtent : -87.04154027, -80.0361, 26.7061, 34.82 (xmin, xmax, ymin, ymax)
ext(altitude)
SpatExtent : -100, -50, 0, 50 (xmin, xmax, ymin, ymax)
ext(florida)
SpatExtent : -1110382.95, 1298632.755, 77020.924, 2466419.852 (xmin, xmax, ymin, ymax)
ext(florida)
SpatExtent : -1110382.95, 1298632.755, 77020.924, 2466419.852 (xmin, xmax, ymin, ymax)
Well, that's weird.
The Florida extents look wild. Why?
crs() provides a valuable clue.crs(shermanSquirrelsShp)
[1] ""
crs(altitude)
[1] "COMPOUNDCRS[\"WGS 84 + EGM2008 height\",\n GEOGCRS[\"WGS 84\",\n DATUM[\"World Geodetic System 1984\",\n ELLIPSOID[\"WGS 84\",6378137,298.257223563,\n LENGTHUNIT[\"metre\",1]]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n CS[ellipsoidal,2],\n AXIS[\"geodetic latitude (Lat)\",north,\n ORDER[1],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n AXIS[\"geodetic longitude (Lon)\",east,\n ORDER[2],\n ANGLEUNIT[\"degree\",0.0174532925199433]],\n ID[\"EPSG\",4326]],\n VERTCRS[\"EGM2008 height\",\n VDATUM[\"EGM2008 geoid\"],\n CS[vertical,1],\n AXIS[\"gravity-related height\",up,\n LENGTHUNIT[\"metre\",1]],\n ID[\"EPSG\",3855]]]"
The Florida extents look wild. Why?
crs() provides a valuable clue.crs(florida)
[1] "PROJCRS[\"NAD83(HARN) / Florida West (ftUS)\",\n BASEGEOGCRS[\"NAD83(HARN)\",\n DATUM[\"NAD83 (High Accuracy Reference Network)\",\n ELLIPSOID[\"GRS 1980\",6378137,298.257222101,\n LENGTHUNIT[\"metre\",1]],\n ID[\"EPSG\",6152]],\n PRIMEM[\"Greenwich\",0,\n ANGLEUNIT[\"Degree\",0.0174532925199433]]],\n CONVERSION[\"unnamed\",\n METHOD[\"Transverse Mercator\",\n ID[\"EPSG\",9807]],\n PARAMETER[\"Latitude of natural origin\",24.3333333333333,\n ANGLEUNIT[\"Degree\",0.0174532925199433],\n ID[\"EPSG\",8801]],\n PARAMETER[\"Longitude of natural origin\",-82,\n ANGLEUNIT[\"Degree\",0.0174532925199433],\n ID[\"EPSG\",8802]],\n PARAMETER[\"Scale factor at natural origin\",0.999941176470588,\n SCALEUNIT[\"unity\",1],\n ID[\"EPSG\",8805]],\n PARAMETER[\"False easting\",656166.666666667,\n LENGTHUNIT[\"US survey foot\",0.304800609601219],\n ID[\"EPSG\",8806]],\n PARAMETER[\"False northing\",0,\n LENGTHUNIT[\"US survey foot\",0.304800609601219],\n ID[\"EPSG\",8807]]],\n CS[Cartesian,2],\n AXIS[\"(E)\",east,\n ORDER[1],\n LENGTHUNIT[\"US survey foot\",0.304800609601219,\n ID[\"EPSG\",9003]]],\n AXIS[\"(N)\",north,\n ORDER[2],\n LENGTHUNIT[\"US survey foot\",0.304800609601219,\n ID[\"EPSG\",9003]]]]"
One of the most important things to remember when dealing with spatial data!!
A projection is a mathematical transformation of coordinates from the surface of a sphere on a 2D plane.
There are always tradeoffs
First, the points object, which was missing its projection.
# Defining coordinate reference system
crs(shermanSquirrelsShp) <- crs(altitude)
Next, reprojecting squirrels and altitude so they match Florida.
# Reprojecting the shapefiles
shermanSquirrelsShp <- project(shermanSquirrelsShp,
crs(florida))
altitude_prj <- project(altitude, crs(florida))
Now it's your turn.
# Defining coordinate reference system
crs(shermanSquirrelsShp) <- crs(altitude)
# Reprojecting the shapefiles
shermanSquirrelsShp <- project(shermanSquirrelsShp,
crs(florida))
altitude_prj <- project(altitude, crs(florida))
plot(altitude_prj, main="Altitude",
xlim = c(-1500000, 1400000),
ylim = c(20000, 4000000))
plot(shermanSquirrelsShp,
pch = 16, cex = 1.5,
add = T)
plot(florida,
add = T)

ext(florida)
SpatExtent : -1110382.95, 1298632.755, 77020.924, 2466419.852 (xmin, xmax, ymin, ymax)
ext(altitude_prj)
SpatExtent : -6029079.69455232, 13007242.2260667, -8832918.89597141, 11015964.863511 (xmin, xmax, ymin, ymax)
altitudeCropped <- crop(altitude_prj,
florida)
Mismatching extents results in errors
altitude_prj - altitudeCropped
Error: [-] extents do not match
NA values
mask(inverse=TRUE)
altitudeCM <- mask(altitudeCropped,
mask = florida)
plot(altitudeCM,
main="Altitude",
buffer = TRUE)
plot(florida, add = TRUE)
plot(shermanSquirrelsShp,
add = TRUE)
#Save result as a .tif file
writeRaster(altitudeCM,
filename = "AltitudeCroppedFlorida.tif",
overwrite = TRUE)
Now it's your turn! Try cropping and masking the altitude_prj object to the Florida shapefile.
#Cropping rasters to extent of shapefile
altitudeCropped <- crop(altitude_prj, florida)
altitudeCM <- mask(altitudeCropped, mask = florida)
plot(altitudeCM, col = viridis::mako(n = 11),
background = "gray")
plot(florida, border = "orange", lwd = 2, add = TRUE)
Suppose we are only interested in the occurrence points for Sherman's Fox Squirrels in Florida.
is.related() returns a vector of logical states

is.related() returns a vector of logical states
floridaSquirrelsInFL <- is.related(shermanSquirrelsShp, florida, relation = "within")
paste0("There are ", sum(!floridaSquirrelsInFL), " squirrels outside of Florida.")
[1] "There are 3 squirrels outside of Florida."
plot(shermanSquirrelsShp, main = "Sherman Squirrels of Florida",
col = ifelse(floridaSquirrelsInFL, "red", "black"))
plot(florida, add = TRUE)
There are three points to remove.
flShermanSquirrelsShp <- shermanSquirrelsShp[floridaSquirrelsInFL]
Now it's your turn! Try to remove all the points outside the Florida shapefile
is.related to find points within Florida
floridaSquirrelsInFL <- is.related(shermanSquirrelsShp, florida, relation = "within")
paste0("There are ",
sum(!floridaSquirrelsInFL),
" squirrels outside of Florida.")
[1] "There are 3 squirrels outside of Florida."
flShermanSquirrelsShp <- shermanSquirrelsShp[floridaSquirrelsInFL]
plot(shermanSquirrelsShp,
main = "Sherman Squirrels of Florida",
col = ifelse(floridaSquirrelsInFL,
"red", "black"))
plot(florida, add = TRUE)
Data from rasters can be extracted using shapefiles
flShermanSquirrelsShp$Elevation <- extract(x = altitudeCM,
y = flShermanSquirrelsShp,
ID = FALSE)
head(flShermanSquirrelsShp)
Species Elevation
1 Sciurus niger 8.967775
2 Sciurus niger 16.983557
3 Sciurus niger 18.863676
4 Sciurus niger 18.863676
5 Sciurus niger 18.863676
6 Sciurus niger 48.562069
Now let's create a new data.frame from the squirrel shapefile and save it as a .csv for futher use later.
flShermanSquirrels_Coords <- geom(flShermanSquirrelsShp)[,c("x", "y")] # Gets coordinates
flShermanSquirrels_Data <- data.frame(flShermanSquirrelsShp) # Gets shapefile data
flShermanSquirrels_Table <- cbind(flShermanSquirrels_Coords, flShermanSquirrels_Data)
colnames(flShermanSquirrels_Table)[1:2] <- c("Longitude", "Latitude")
head(flShermanSquirrels_Table)
Longitude Latitude Species Elevation
1 463183.1 1504052 Sciurus niger 8.967775
2 828404.8 1591736 Sciurus niger 16.983557
3 916739.2 1472025 Sciurus niger 18.863676
4 917076.2 1472125 Sciurus niger 18.863676
5 917166.3 1472115 Sciurus niger 18.863676
6 672360.8 1347055 Sciurus niger 48.562069
write.csv(flShermanSquirrels_Table, "shermanSquirrelsFlorida.csv", row.names = FALSE)
Data from rasters can be extracted using shapefiles
florida$ElevationMax <- extract(x = altitudeCM,
y = florida,
fun = max,
ID = FALSE)
head(florida)
OBJECTID DATESTAMP SHAPE_AREA SHAPE_LEN ElevationMax
1 1 2000/05/16 1.56987e+12 28344148 92.96682
Data from rasters can be extracted using shapefiles
# Generate 1000 random sampling points within Florida
floridaSample <- spatSample(florida, size = 1000)
# Extract elevation values
floridaSample$Elevation <- extract(x = altitude_prj, y = florida, ID = FALSE)
Now it's your turn!
1. Extract elevation to squirrel occurrence points
2. Sample 1000 random points from accross Florida and extract elevation.
3. Convert both to data.frame objects and save them.
Squirrels
flShermanSquirrelsShp$Elevation <- extract(x = altitudeCM,
y = flShermanSquirrelsShp,
ID = FALSE)
flShermanSquirrels_Coords <- geom(flShermanSquirrelsShp)[,c("x", "y")]
flShermanSquirrels_Data <- data.frame(flShermanSquirrelsShp)
flShermanSquirrels_Table <- cbind(flShermanSquirrels_Coords, flShermanSquirrels_Data)
colnames(flShermanSquirrels_Table)[1:2] <- c("Longitude", "Latitude")
Florida
floridaSample <- spatSample(florida, size = 1000)
floridaSample$Elevation <- extract(x = altitude_prj, y = florida, ID = FALSE)
floridaSample_Coords <- geom(floridaSample)[,c("x", "y")]
floridaSample_Data <- data.frame(floridaSample)
floridaSample_Table <- cbind(floridaSample_Coords, floridaSample_Data)
colnames(floridaSample_Table)[1:2] <- c("Longitude", "Latitude")
Save results.
write.csv(flShermanSquirrels_Table, "shermanSquirrelsFlorida.csv", row.names = FALSE)
write.csv(floridaSample_Table, "FloridaNull.csv", row.names = FALSE)
Now we have data and can do some statistics.

# Prepare data
floridaSampleDataframe <- data.frame("Sample" = "Florida",
"Elevation" = floridaSample$Elevation)
squirrelSampleDataframe <- data.frame("Sample" = "Present",
"Elevation" = flShermanSquirrels_Table$Elevation)
comparisonData <- rbind(floridaSampleDataframe, squirrelSampleDataframe)
comparisonData <- comparisonData[complete.cases(comparisonData),]
How do the data look?
# Plot data
boxplot(comparisonData$Elevation[comparisonData$Sample == "Florida"],
comparisonData$Elevation[comparisonData$Sample == "Present"],
names = c("Florida", "Present"),
main = "Elevation at Sherman Fox Squirrel Observations")
How about a t-test?
statTest <- t.test(Elevation ~ Sample, data = comparisonData)
statTest
Welch Two Sample t-test
data: Elevation by Sample
t = 13.966, df = 31.099, p-value = 6.078e-15
alternative hypothesis: true difference in means between group Florida and group Present is not equal to 0
95 percent confidence interval:
31.42083 42.16544
sample estimates:
mean in group Florida mean in group Present
53.45219 16.65905
Now it's your turn! 1. Make boxplots of elevation at Sherman's Fox Squirrel occurrence points and the null distribution you generated for Florida 2. Do a t-test of the results. 3. Bonus challenge: Can you think of other ways of visualizing or analyzing the data?
# Plot data
boxplot(comparisonData$Elevation[comparisonData$Sample == "Florida"],
comparisonData$Elevation[comparisonData$Sample == "Present"],
names = c("Florida", "Present"),
main = "Elevation at Sherman Fox Squirrel Observations")
statTest <- t.test(Elevation ~ Sample, data = comparisonData)
statTest
Welch Two Sample t-test
data: Elevation by Sample
t = 13.966, df = 31.099, p-value = 6.078e-15
alternative hypothesis: true difference in means between group Florida and group Present is not equal to 0
95 percent confidence interval:
31.42083 42.16544
sample estimates:
mean in group Florida mean in group Present
53.45219 16.65905
R cannot replace everything that GUI GIS software does, but…
Hijmans R (2023). terra: Spatial Data Analysis. R package version 1.7-55, https://CRAN.R-project.org/package=terra.

# Generate presence/absence data
comparisonData$Response <- ifelse(comparisonData$Sample == "Florida",
yes = 0, no = 1)
# Rename altitude response
names(altitudeCM) <- "Elevation"
squirrelGLM <- glm(Response ~ Elevation,
data = comparisonData, family = binomial(),)
squirrelGLM
Call: glm(formula = Response ~ Elevation, family = binomial(), data = comparisonData)
Coefficients:
(Intercept) Elevation
1.7120 -0.1516
Degrees of Freedom: 1029 Total (i.e. Null); 1028 Residual
Null Deviance: 271.3
Residual Deviance: 133.3 AIC: 137.3
predict takes environmental data (object) and a model (model)squirrelGLM_prediction <- predict(object = altitudeCM, model = squirrelGLM)
plot(squirrelGLM_prediction)
plot(flShermanSquirrelsShp, pch = 16, add = T)
threshold <- min(extract(squirrelGLM_prediction,
flShermanSquirrelsShp,
ID = FALSE),
na.rm = TRUE)
Where are we most likely to find Sherman Squirrels based on our model?
plot(squirrelGLM_prediction > threshold)
plot(flShermanSquirrelsShp, pch = 16, add = T)