Review
Today’s lab is relatively short, before we begin we will do some interactive review of the lab material we have covered to date. Open the following link on one of the lab computers to follow along https://poll.gis.unbc.ca/p/31359493
Moran’s I
In today’s lab we will be looking at calculating Moran’s I, and Local Moran’s I using R; while in R we will also take a look at how we can produce reproducible reports using R-Markdown documents. For those unfamiliar Markdown is a tool used by various programs that allow for documents to be written in plain text, and rendered with various style options that were defined in said text.
In RStudio you can create your first Markdown document by going into File, New and choosing R Notebook.

The main parts of the new new document are
- The heading starts and ends with —
- Markdown (Think of this as plain text
- R Code surrounded by “`
{r}
“`
It is generally considered proper form to include your name and date in the file header.
---
title: "Morans-I"
author: "Matt McLean"
date: "07/11/2022"
output:
html_notebook: default
pdf_document: default
---
With that in place you can preview your document with the preview button

Finally the Table of Contents is another very helpful tool allowing the document to get some structure, outside of r code this is done with the # symbol, for example,
xxxxxxxxxx
# Heading 1
## Heading 2
# H1 Two
## H2 Two
### Heading 3
Will produce the following headings, these will both appear in the final document, and can also be used for navigating within the document inside of R Studio, by clicking on the desired heading.

Preparing for Moran’s-I
For today’s lab we will be calculating Moran’s-I on some trials conducted by the Ministry of Forests, and will be using a package not normally available in R, this will be installed in the R command line with the following command.
xxxxxxxxxx
install.packages('bcmapsdata', repos='https://bcgov.github.io/drat/')
After this is installed you can import the libraries that we will be using today. Note there is an argument include=FALSE, this will prevent all these import statements from appearing in our final report.
xxxxxxxxxx
```{r, include=FALSE}
library(bcdata)
library(bcmaps)
library(bcmapsdata)
library(tidyverse)
library(sf)
library(ggplot2)
library(ape)
library(units)
library(lubridate)
library(spdep)
library(sp)
library(tmap)
library(rgdal)
```
Download the trial data from GitHub https://github.com/FLNRO-Smithers-Research/OffSite_Trial_Reporting/blob/master/trial_data.Rdata
After you have downloaded into your working directory you can open with the following. It is relatively large code, as it expands the data from a single data-frame to multiple data-frames as well as calculate the extent of data (ie how many years does the data cover).
xxxxxxxxxx
```{r}
load('trial_data.Rdata')
seedlots = all_data$seedlots
trees = all_data$trees
climate = all_data$climate
plots = all_data$plots
meta = all_data$meta
sites = distinct(select(merge(meta, plots, by="FID"), Location, FID, Lat, Lon), Location, FID, .keep_all = TRUE) #Location based on a plot not center of entire site
climate_sites = distinct(climate, ClimateStationID)
#List of years for which tree data is present, don't try to plot dates outside this range
years = year(as.character(min(as.Date(as.character(trees$Date))))):year(as.character(max(as.Date(as.character(trees$Date)))))
#List of years for climate data can differ from plot data.
climate_years = year(as.character(min(as.Date(as.character(climate$Date))))):year(as.character(max(as.Date(as.character(climate$Date)))))
#This function was used by many of the plots, moved here to not repeat
tree_summary=list()
for(year in years){
tree_summary[[year]] <- trees %>%
filter(year(as.character(Date)) == year) %>%
# need to convert seedlot to factor
mutate(Seedlot=factor(Seedlot)) %>%
# Relevel condition factors
mutate(Condition=fct_relevel(Condition,"Missing","Dead", "Moribund","Poor","Fair","Good")) %>% # reorder levels
# Create plot summaries
group_by(FID,Plot,Species,Seedlot,Condition) %>%
summarise(n=n()) %>% # summarize number of trees at FID/Plot/Species/Condition
ungroup()
}
```
To prepare for calculating the statistics the data must be opened, this sample data set contains 3 attributes that we can use to check for spatial auto correlation with Species, Seedlot (which tree farm are the seedlings from), and condition. You may note that there are also height and diameter columns, while potentially interested this data has been scrubbed from the open source side of the project.
xxxxxxxxxx
attribute_list <- c('Species', 'Seedlot', 'Condition')
Next the data needs to be sorted by trial site, (Identified by FID)
xxxxxxxxxx
# Get list of unique trial sites
fids = as.data.frame(trees %>% distinct(FID) )
# Fore each trial site
for(id in 1:nrow(fids)) {
# Convert iterator back to factor
feature <- as.vector(fids$FID[[id]])
# Select trees at trial site and that have a location
tree_list = as.data.frame(trees %>% filter(FID == feature & (Lon != 0 | Lat != 0)))
if (nrow(tree_list) > 0){
# Print trial site name
cat(" \n####", as.character(feature), " \n")
}
else{ # No trees were found in filter
cat(" \n", as.character(feature)", tree positions unknown")
}
}
To compute Moran’s I we need to convert the Trees to SF features and project into UTM
xxxxxxxxxx
tree_sf <- st_as_sf(tree_list, coords = c('Lat', 'Lon'))
st_crs(tree_sf) <- 4326
tree_utm <- st_transform(tree_sf, 32610)
Once the data is sf the next stage is to calculate the inverse distance of all points
xxxxxxxxxx
# Compute distance between points
knn <- knn2nb(knearneigh(tree_utm))
# Determine maximum Neighbor Distances
all.linked<- max(unlist(nbdists(knn, tree_utm)))
# Make Nerest Neighbor List
tree_dist <- dnearneigh(tree_utm, 0, all.linked)
# Style W is row-wise normalization
lw <- nb2listw(tree_dist, style="W", zero.policy = T)
When working with categorical data it will be converted to factor in R, for proper calculation we would like this numbers to have a logical pattern, as such we will re-level the condition attribute
xxxxxxxxxx
tree_list %>% mutate(Condition=fct_relevel(Condition,"Missing","Dead", "Moribund","Poor","Fair","Good"))
Finally we want to plot the attribute and calculate Moran’s I
xxxxxxxxxx
for (att in attribute_list){
p <- ggplot(tree_utm) + geom_sf(aes(col = .data[[att]])) +
coord_sf(datum = st_crs(tree_utm)) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5)) +
ggtitle(paste(feature, "-", att))
print(p)
# Test Morans I
test <- moran.test(as.numeric(tree_utm[[att]]), lw)
print(test)
# Monty Carlo method
MI <- moran.mc(as.numeric(tree_utm[[att]]), lw, nsim = 599, zero.policy = T)
print(MI)
}
This is put all together as the following function
xxxxxxxxxx
attribute_list <- c('Species', 'Seedlot', 'Condition')
fids = as.data.frame(trees %>% distinct(FID) )
for(id in 1:nrow(fids)) {
feature <- as.vector(fids$FID[[id]])
tree_list = as.data.frame(trees %>% filter(FID == feature & (Lon != 0 | Lat != 0)))
if (nrow(tree_list) > 0){
cat(" \n####", as.character(feature), " \n")
tree_sf <- st_as_sf(tree_list, coords = c('Lat', 'Lon'))
st_crs(tree_sf) <- 4326
tree_utm <- st_transform(tree_sf, 32610)
# Compute distance between points
knn <- knn2nb(knearneigh(tree_utm))
# Determine maximum Neighbor Distances
all.linked<- max(unlist(nbdists(knn, tree_utm)))
# Make Nerest Neighbor List
tree_dist <- dnearneigh(tree_utm, 0, all.linked)
# Style W is row-wise normalization
lw <- nb2listw(tree_dist, style="W", zero.policy = T)
tree_list %>% mutate(Condition=fct_relevel(Condition,"Missing","Dead", "Moribund","Poor","Fair","Good"))
for (att in attribute_list){
p <- ggplot(tree_utm) + geom_sf(aes(col = .data[[att]])) +
coord_sf(datum = st_crs(tree_utm)) +
theme(axis.text.x = element_text(angle = 45, vjust = 0.5)) +
ggtitle(paste(feature, "-", att))
print(p)
# Test Morans I
test <- moran.test(as.numeric(tree_utm[[att]]), lw)
print(test)
# Monty Carlo method
MI <- moran.mc(as.numeric(tree_utm[[att]]), lw, nsim = 599, zero.policy = T)
print(MI)
}
}
else{
cat(" \nTree positions unknown")
}
cat(" \n")
}
Assignment
Create a Report on the Spatial Auto-correlation between Annual Precipitation and location of weather station using the stations data found in L:\GEOG413\lab08
You should be able to
- Import the data, and confirm the CRS / Translate if Needed
- Plot the data on a basemap (note that as you are making a PDF report interactive leaflet will not be an option)
- Report on if there is a correlation, and if it is significant.