Presentations project 1: Starting at 8:15 AM

Monday October 5th

  • Sarah Abdelghany
  • Meshal Alrwaished
  • Jiya Hai
  • Rena Halawi Ghosn
  • Dzulfiqar Fawwaz

Thursday October 8th

  • Shaymaa Mahmoud
  • Yassine Regayeg
  • Irfan Shahmard
  • Kaichuang Yang
  • Amina Manseur


Presentations will be 10 minutes plus 5 minutes questions.

Course schedule

No class on 5th and 26th November

Week 1: Introduction to Spatial Data Science with R
Week 2-3: Areal data
Week 4: Dashboards
Week 5: Geostatistical data
Week 6: Student presentations project 1
Week 7: Geostatistical data
Weeek 8-10: Point patterns
Week 11: Student presentations project 2
Week 12-13: Point patterns
Week 14: Interactive web applications
Week 15: Student presentations project 3

Evaluation

General guidelines

The evaluation of STAT 300 consists of 3 projects to be done individually:

  • Project 1: Areal data analysis (33%). Due date October 4 by 5pm
  • Project 2: Geostatistical data analysis (33%). Due date November 8 by 5pm
  • Project 3: Point pattern data analysis (34%). Due date December 6 by 5pm

For each of the projects, students will look for an appropriate dataset and analyze it using the statistical techniques learned during the course. They will need to submit a written report and reproducible R code, and give an oral presentation about their work.

Students will need to submit their work via Blackboard by the due date. Late coursework will not be accepted, unless prior arrangements have been made.

Files to be submitted

Students will need to submit via Blackboard a zip file called LastnameFirstname.zip containing the following:

  • written report in PDF format (LastnameFirstname.pdf),
  • folder data with the data,
  • folder code with reproducible R code (code.R). The code should load data that is in the data folder, work without errors, and include comments,
  • presentation to be shown in the oral presentation (PDF, HTML or PowerPoint) (LastnameFirstnamePresentation.pdf).

Written report

The written report will include a title, abstract, introduction, data, methods, results and interpretation, a brief discussion section, and references. Make sure you wholly and accurately interpret your results, tables and figures in the context of the study. The report does not have to include R code. The R code will be submitted separately. Tables and figures captions need to be self-contained. For the report, please use a maximum of 10 pages with 12 point font, one column, and 2cm margins, including tables and figures. Reports that exceed the specified number of pages will not be assessed. You will be judged on the quality and not on the quantity of material you submit.

Oral presentation

In the oral presentation you can use slides, animations and/or dashboards to show your work. You should show good communication and presentation skills. Presentations will be 10 minutes plus 5 minutes questions.

Data

The data can be from your own research (and that you have permission to openly share) or data that you can find in open repositories. The website https://rspatialdata.github.io/ can be helpful to find data. Examples of data that can be used for each of the projects are the following.

Project 1: Areal data

Areal data about deaths counts of influenza, pneumonia, and coronavirus disease 2019 (COVID-19) in USA by week ending date, region, and age group can be found at https://data.cdc.gov/NCHS/Provisional-Death-Counts-for-Influenza-Pneumonia-a/ynw2-4viq. Other public health datasets are available at https://data.cdc.gov/.

Project 2: Geostatistical data

An example of geostatistical data would be air quality data in the US measured at specific monitoring stations. These data can be found at https://www.epa.gov/outdoor-air-quality-data.

Project 3: Point pattern data

Point pattern datasets can be found in the spatstat package. For example, data(betacells) contains data of cells in the retina.

Evaluation

The project will be marked out of 100. There is no single correct analysis for this project, so you will not be scored based on how close you get to one particular answer. Instead, the marks will be allocated according to the following principles.

  • 70-100 A project that could be presented with little or no revision. Analysis should be soundly done so that conclusions are well supported statistically. Interpretation should be reasonably mature. The project should demonstrate a clear overview of the work without getting lost in details and be free of minor statistical errors.
  • 60-69 A project that could be presented after a round of revision, but without having to re-do much of the actual analysis. Some substantive flaws in the analysis or presentation (or more minor flaws in both), but sound. A good grasp of the statistics and context, so that interpretation is reasonable.
  • 50-59 Major re-working required before the project could be presented, but containing some sound statistics demonstrating understanding of statistical methods and their application. Good presentation and organization.
  • 40-49 Major flaws in analysis and presentation, but demonstrating some understanding of statistical methods and a reasonable attempt to present the results.
  • 0-39 Flawed analysis demonstrating little or no understanding of statistical methods, and/or incomprehensible or very poorly organized presentation.

Questions?

Course schedule

Week 1: Introduction to Spatial Data Science with R
Week 2: Areal data: modeling
Week 3: Areal data: neighborhood matrices, spatial autocorrelation. R packages for making maps and downloading open spatial data
Week 4: Interactive dashboards. Saudi National Day
Week 5: Geostatistical data: spatial interpolation methods
Week 6: Student presentations project 1
Week 7: Geostatistical data: modeling. Spatially misaligned data
Week 8: Midsemester break. Point patterns
Week 9: Point patterns: simulation, complete spatial randomness
Week 10: Point patterns: K-function
Week 11: Student presentations project 2
Week 12: Point patterns: intensity
Week 13: Point patterns: modeling
Week 14: Interactive web applications
Week 15: Student presentations project 3

Questions?

Spatial Data Science with R


Paula Moraga, Ph.D. 

Associate Professor of Statistics

King Abdullah University of Science
and Technology (KAUST), Saudi Arabia

   www.PaulaMoraga.com

a png
       a png

Books

Introductions

Class meetings

Mondays and Thursdays

8:30 AM - 10:00 AM

Building 9, Room 4228


Lecturer

Dr. Paula Moraga

E-mail: paula.moraga@kaust.edu.sa


Teaching Assistant

Mr. Mohammad Saqib Ansari

E-mail: mohammad.ansari@kaust.edu.sa

Course overview

We will learn statistical methods, modeling approaches, and visualization techniques to analyze spatial data using R

  • R packages for retrieval, manipulation and visualization of spatial data

  • Statistical methods to analyze areal, geostatistical and point pattern data

  • Interactive visualizations and dashboards to communicate results

Course materials

Presentation: https://www.paulamoraga.com/presentation-course

Geospatial Health Data: Modeling and Visualization with R-INLA & Shiny (2019)
https://www.paulamoraga.com/book-geospatial/

Spatial Statistics for Data Science: Theory and Practice with R (2023)
https://www.paulamoraga.com/book-spatial/

Geospatial Health Data: Modeling and Visualization (2019) http://www.paulamoraga.com/book-geospatial/

   
  • Manipulate and transform point, areal, raster data, create maps with R

  • Fit and interpret Bayesian spatial, spatio-temporal models with INLA, SPDE

  • Interactive visualizations, reproducible reports, dashboards and Shiny apps

Spatial Statistics for Data Science: Theory and Practice with R (2023) http://www.paulamoraga.com/book-spatial/

  • Spatial data: types, retrieval, manipulation and visualization. Statistical methods and models to analyze spatial data using R

  • Areal data: spatial neighborhood matrices, autocorrelation, models

  • Geostatistical data: interpolation, kriging, model-based geostatistics

  • Point patterns: intensity estimation, clustering, point process models

  • Reproducible examples in environment, ecology, epidemiology, crime, real estate

R packages

install.packages(c("sf", "terra", "geodata", "rnaturalearth", "spdep",
                   "dplyr", "SpatialEpi", "wbstats", "flexdashboard", 
                   "ggplot2", "viridis", "RColorBrewer", "patchwork", "DT", 
                   "leaflet", "mapview", "leafpop", "leafsync", "rasterVis"))

install.packages("INLA",
repos = c("https://inla.r-inla-download.org/R/stable", "https://cloud.r-project.org"), dep = TRUE)

Geospatial data and methods

John Snow’s map of cholera, London, 1854

Geospatial methods for disease surveillance

Geospatial methods use data on disease cases, population at risk, and risk factors such as environmental, climate and socio-economic variables to

  • Understand geographic and temporal patterns
  • Identify potential risk factors
  • Highlight high risk areas and detect clusters
  • Measure inequalities
  • Early detection of outbreaks

🗣 Results can be communicated using maps and other visualizations

✍️ Results guide decision-makers to better allocate limited resources and to design strategies for disease prevention and control

🌍 Many methods useful in fields such as ecology, environment, criminology

Types of spatial data

Moraga and Lawson, Computational Statistics & Data Analysis, 2012
Moraga et al., Parasites & Vectors, 2015
Moraga and Montes, Statistics in Medicine, 2011

Areal data


Model for disease relative risk \(\theta_i\) in areas

\[Y_i|\theta_i \sim Poisson(E_i \times \theta_i)\] \[\log(\theta_i) = \boldsymbol{z}_i \boldsymbol{\beta} + u_i + v_i\]

Fixed effects quantify the effects of the covariates on the disease risk

Random effects represent residual variation not explained by the covariates. \(u_i\) spatial effect to account for spatial dependence between relative risks. \(v_i\) unstructured effect to account for independent noise.

Moraga and Lawson, Computational Statistics & Data Analysis, 2012

Geostatistical data

Moraga, et al., Parasites & Vectors, 2015

Geospatial modeling of lymphatic filariasis prevalence in sub-Saharan Africa

Lymphatic filariasis caused by microscopic worms and transmitted by mosquitoes

Main strategy against the disease is Mass Drug Administration. Resources are limited and need to decide which areas most in need

Geospatial modeling of lymphatic filariasis

\[Y_i|P(\boldsymbol{x}_i)\sim \mbox{Binomial} (n_i, P(\boldsymbol{x}_i)),\ \ \ \mbox{logit}(P(\boldsymbol{x}_i)) = \boldsymbol{z}_i \boldsymbol{\beta} + S(\boldsymbol{x}_i) + u_i\]

Covariates based on characteristics known to affect disease transmission (temperature, precipitation, vegetation, elevation, land cover, population, etc.). Random effects model residual variation not explained by covariates

Point patterns


Assume point pattern \(\{s_i: i=1, \ldots, n\}\) has been generated as a realization of a point process \(Z = \{Z(s): s \in \mathbb{R}^2\}\)


A point process model can be used to estimate the intensity of events, identify patterns in the distribution of the observed locations, and learn about the correlation between the locations and spatial covariates

Moraga and Montes, Statistics in Medicine, 2011

Types of spatial data

Moraga and Lawson, Computational Statistics & Data Analysis, 2012
Moraga et al., Parasites & Vectors, 2015
Moraga and Montes, Statistics in Medicine, 2011

Exercise

State whether each of the following is an example of areal data, geostatistical data or a point pattern.

  1. Geographical locations of the home addresses of a sample of cardiovascular patients in Italy.

  2. Yesterday’s mid-day temperature at the centres of fifty cities in Germany.

  3. Carbon dioxide levels measured at twenty locations near a road.

  4. The positions of hair follicles on a human head.

  5. Crop yields from each of thirteen adjacent rows of wheat in a field.

  6. Positions of snooker balls on a snooker table after one shot has been played.

  7. The average house price in each postcode region in Paris.

Spatial data and
visualization with R

Spatial data in R

Spatial data can be represented using vector and raster data

Vector data displays points, lines and polygons, and associated information
Examples: locations of monitoring stations, road networks, municipalities

Raster data are regular grids with cells of equal size that are used to store values of spatially continuous phenomena
Examples: elevation, temperature, air pollution values

R packages: sf (vector data) and terra (raster and vector data)

sf to work with vector data

Vector data are often represented using a data format called shapefile.

library(sf)
pathshp <- system.file("shape/nc.shp", package = "sf")
map <- st_read(pathshp, quiet = TRUE)
plot(map[1])

Shapefile to store vector data

A shapefile is a collection files.

terra to work with raster (and vector) data

Raster data often come in GeoTIFF format which has extension .tif.

library(terra)
pathraster <- system.file("ex/elev.tif", package = "terra")
r <- terra::rast(pathraster)
plot(r)

R packages to download open spatial data

Chapter 6 Book Spatial Statistics for Data Science

https://rspatialdata.github.io/

Data R package Database
Administrat. boundaries rnaturalearth Natural Earth
Population wopr WorldPop
OpenStreetMap osmdata OpenStreetMap (OSM)
Elevation elevatr AWS Terrain Tiles
Temperature geodata WorldClim
Humidity nasapower NASA-POWER Project
Vegetation MODIStsp MODIS
Land cover MODIStsp MODIS
Malaria malariaAtlas Malaria Atlas Project (MAP)

Coordinate Reference Systems (CRS)

  1. unprojected or geographic: Latitude and Longitude for referencing location on the ellipsoid Earth
    (decimal degrees (DD) or degrees, minutes, and seconds (DMS))

  2. projected: Easting and Northing for referencing location on 2-dimensional representation of Earth
    Common projection: Universal Transverse Mercator (UTM)
    Location is given by the zone number (60 zones), hemisphere (north or south), and Easting and Northing coordinates in the zone in meters

                

Coordinate Reference Systems (CRS)

Most common CRSs can be specified with their EPSG (European Petroleum Survey Group) codes

EPSG 4326 refers to the geographic CRS (latitude and longitude)

Common spatial projections: https://spatialreference.org/ref/

Geographic coordinates of New York City, USA:

Latitude Longitude
Degrees, Minutes and Seconds 40° 43’ 50.1960’’ North 73° 56’ 6.8712’’ West
Decimal degrees (North/South and West/East) 40.730610° North 73.935242° West
Decimal degrees (Positive/Negative) 40.730610 -73.935242

Visualizing spatial data

Making maps with R

Chapter 5 Book Spatial Statistics for Data Science


Example data: sudden infant deaths in the counties of North Carolina, USA, in 1974 and 1979 from the sf package

library(sf)
nameshp <- system.file("shape/nc.shp", package = "sf")
d <- st_read(nameshp, quiet = TRUE)
d$vble <- d$SID74
d$vble2 <- d$SID79

ggplot2

library(ggplot2)
library(viridis)
ggplot(d) + geom_sf(aes(fill = vble)) +
  scale_fill_viridis() + theme_bw()

leaflet

library(leaflet)
pal <- colorNumeric(palette = "YlOrRd", domain = d$vble)
leaflet(d) %>% addTiles() %>%
  addPolygons(color = "white", fillColor = ~ pal(vble), fillOpacity = 0.8) %>%
  addLegend(pal = pal, values = ~vble, opacity = 0.8)

mapview

library(mapview)
mapview(d, zcol = "vble")

Side-by-side plots with mapview

library(leaflet.extras2) # | operator
library(RColorBrewer)
pal <- colorRampPalette(brewer.pal(9, "YlOrRd")) # common legend
at <- seq(min(c(d$vble, d$vble2)), max(c(d$vble, d$vble2)), length.out = 8)
m1 <- mapview(d, zcol = "vble", map.types = "CartoDB.Positron", col.regions = pal, at = at)
m2 <- mapview(d, zcol = "vble2", map.types = "CartoDB.Positron", col.regions = pal, at = at)
m1 | m2

Synchronized maps with leafsync

library(RColorBrewer)
pal <- colorRampPalette(brewer.pal(9, "YlOrRd")) # common legend
at <- seq(min(c(d$vble, d$vble2)), max(c(d$vble, d$vble2)), length.out = 8)
m1 <- mapview(d, zcol = "vble", map.types = "CartoDB.Positron", col.regions = pal, at = at)
m2 <- mapview(d, zcol = "vble2", map.types = "CartoDB.Positron", col.regions = pal, at = at)
leafsync::sync(m1, m2)

tmap

library(tmap)
tmap_mode("plot") # Interactive with tmap_mode("view")
tm_shape(d) + tm_polygons("vble")

HTML widgets

HTML widgets

Data can also be visualized using HTML widgets which are interactive web visualizations built with JavaScript

Showcase

Leaflet

http://rstudio.github.io/leaflet/     Basemaps

library(leaflet)
pal <- colorNumeric(palette = "YlOrRd", domain = d$vble)
leaflet(d) %>% addTiles() %>%
  addPolygons(color = "white", fillColor = ~ pal(vble), fillOpacity = 0.8) %>%
  addLegend(pal = pal, values = ~vble, opacity = 0.8)

Dygraphs

http://rstudio.github.io/dygraphs/

library(dygraphs)
lungDeaths <- cbind(mdeaths, fdeaths)
dygraph(nhtemp, main = "New Haven Temperatures") %>%
  dyRangeSelector(dateWindow = c("1920-01-01", "1960-01-01"))

DataTable

http://rstudio.github.io/DT/

library(DT)
datatable(iris, options = list(pageLength = 5))

Interactive documents and dashboards for communication

Reproducible documents with R Markdown

R Markdown can be used to turn our analysis into fully reproducible documents that can be shared with others. Output formats include HTML, PDF or Word. An R Markdown file is written with Markdown syntax with embedded R code, and can include narrative text, tables and visualizations

http://www.paulamoraga.com/book-geospatial/sec-rmarkdown.html

Quarto

Quarto can be used to create reproducible documents using multiple languages such as R, Python and Julia. A Quarto document can be rendered as formats like PDF and Word. A Quarto document has extension .qmd and is formed of a YAML header, Markdown text, and R code chunks

Interactive dashboards with flexdashboard

flexdashboard uses R Markdown to publish a group of related data visualizations as a dashboard

http://www.paulamoraga.com/book-geospatial/sec-flexdashboard.html

Shiny web applications

Shiny is a web application framework for R that enables to build interactive web applications

http://www.paulamoraga.com/book-geospatial/sec-shiny.html

SpatialEpiApp is a Shiny app for disease risk estimation, cluster detection, and interactive visualization

https://paulamoraga.shinyapps.io/spatialepiapp/

Geospatial modeling

Integrated nested Laplace approximation (INLA)

INLA

Integrated nested Laplace approximation (INLA) is a computational approach to perform approximate Bayesian inference in latent Gaussian models such as generalized linear mixed models and spatial and spatio-temporal models

INLA uses a combination of analytical approximations and numerical integration to obtain approximated posterior distributions of parameters and is very fast compared to MCMC. The combination of INLA and the stochastic partial differential equation (SPDE) approaches permits to analyze point data

INLA and SPDE can be applied with the R package R-INLA

Resources

INLA website http://www.r-inla.org/

Geospatial Health Data: Modeling and Visualization with R-INLA and Shiny. Moraga. CRC Press, 2019 https://www.paulamoraga.com/book-geospatial/

Advanced Spatial Modeling with SPDE Using R and INLA. Krainski et al. CRC Press, 2019 https://becarioprecario.bitbucket.io/spde-gitbook/

Latent Gaussian models

Observations belong to an exponential family with mean \(\mu_i=g^{-1}(\eta_i)\)
(e.g. Gaussian, Poisson, Binomial, …) \[y_i|\boldsymbol{x},\boldsymbol{\theta} \sim \pi(y_i|x_i,\boldsymbol{\theta}),\ i=1, \ldots, n\]

Latent Gaussian field        \(\boldsymbol{x}|\boldsymbol{\theta}\sim N(\boldsymbol{\mu(\theta)},\boldsymbol{Q(\theta)}^{-1})\)

Hyperparameters (not necessarily Gaussian)        \(\boldsymbol{\theta} \sim \pi(\boldsymbol{\theta})\)

Linear predictor accounts for effects of covariates in an additive way

\[\eta_i=\alpha+\sum_{k=1}^{n_{\beta}}\beta_k z_{ki}+\sum_{j=1}^{n_f}f^{(j)}(u_{ji})\]

\(\alpha\) intercept, \(\{\beta_k\}\)’s quantify the linear effects of covariates \(\{z_{ki}\}\) on response, \(\{f^{(j)}(\cdot)\}\)’s set of random effects defined in terms of some covariates \(\{u_{ji}\}\) (e.g. iid, rw1, ar1, CAR, …)

Latent Gaussian variables \(\boldsymbol{x}=(\alpha,\{\beta_k\},\{f^{(j)}\})|\boldsymbol{\theta}\sim N(\boldsymbol{\mu(\theta)},\boldsymbol{Q(\theta)}^{-1})\)

INLA

Compute posterior marginals for latent Gaussian field and hyperparameters

\[\pi(x_i|\boldsymbol{y})=\int \pi(x_i|\boldsymbol{\theta},\boldsymbol{y})\pi(\boldsymbol{\theta}|\boldsymbol{y})d\boldsymbol{\theta},\ \pi(\theta_j|\boldsymbol{y})=\int \pi(\boldsymbol{\theta}|\boldsymbol{y})d\boldsymbol{\theta}_{-j}\]

Use this form to construct nested approximations

\[\tilde \pi(x_i|\boldsymbol{y})=\int \tilde \pi(x_i|\boldsymbol{\theta},\boldsymbol{y})\tilde \pi(\boldsymbol{\theta}|\boldsymbol{y})d\boldsymbol{\theta},\ \tilde \pi(\theta_j|\boldsymbol{y})=\int \tilde \pi(\boldsymbol{\theta}|\boldsymbol{y})d\boldsymbol{\theta}_{-j}\]

This approximation can be integrated numerically with respect to \(\boldsymbol{\theta}\) \[\tilde \pi(x_i|\boldsymbol{y})=\sum_k \tilde \pi(x_i|\boldsymbol{\theta_k},\boldsymbol{y})\tilde \pi(\boldsymbol{\theta_k}|\boldsymbol{y})\times \Delta_k,\ \tilde \pi(\theta_j|\boldsymbol{y})=\sum_l \tilde \pi(\boldsymbol{\theta_l^*}|\boldsymbol{y})\times \Delta_l^*\]

\(\Delta_k\) area weight corresponding to \(\boldsymbol{\theta_k}\)
\(\Delta_l^*\) area weight corresponding to \(\boldsymbol{\theta_l^*}\)

INLA

The approximated posterior distributions \(\tilde \pi (x_i|\boldsymbol{y})\) can be post-processed to compute quantities of interest like posterior expectations and quantiles

        Expectation         95% C.I.

R-INLA package

Install INLA

install.packages("INLA",
repos = "https://inla.r-inla-download.org/R/stable",
dep = TRUE)

library(INLA)

R-INLA package

Model

\[Y_i | \eta_i, \sigma^2 \sim N(\eta_i, \sigma^2), i = 1, \ldots, n\] \[\eta_i = \beta_0 + \beta_1 \times x_{1i} + \beta_2 \times x_{2i} + u_i,\ u_i \sim N(0, \sigma^2_u)\]

  1. Write the linear predictor as a formula object in R
formula <- y ~ x1 + x2 + f(id, model = "iid")

Random effects specified with f(). First argument id is an index vector that specifies the element of the random effect that applies to each observation, second argument model name

  1. Call the function inla()
res <- inla(formula, family = "gaussian", data = d)
  1. Inspect the res object wich contains the fitted model.
    Posteriors can be post-processed using a set of functions provided by R-INLA
summary(res)
res$summary.fixed

Areal data

Areal data


Standardized Mortality Ratio (SMR) is often used to estimate disease risk

\[ SMR_i = \frac{Y_i}{E_i} = \frac{\mbox{number observed cases in area } i}{\mbox{number expected cases in area } i}\]

\(SMR_i = 1\) same number observed as expected
\(SMR_i > 1\) more observed than exp. (high risk)
\(SMR_i < 1\) less observed than exp. (low risk)

Example

\(SMR_i = \frac{Y_i}{E_i} = \frac{200}{100} = 2 > 1\) \(\rightarrow\) area \(i\) high risk (observed \(>\) expected) \(SMR_i = \frac{Y_i}{E_i} = \frac{100}{200} = 0.5 < 1\) \(\rightarrow\) area \(i\) low risk (observed \(<\) expected)

Indirect standardiz. to calculate expected cases

The expected number of cases in area \(i\) are the number of cases one would expect if population in area \(i\) behaved the way the standard pop. behaves

Standard population is considered as the whole population (all areas)

Population is stratified by several factors (e.g., age and gender)

\[E_i = \sum_{j=1}^m r_j^{(std)} n_j^{(i)}\]
  • \(r_j^{(std)}=\frac{\mbox{number cases}}{\mbox{population}}\) rate in stratum \(j\) in the standard population

  • \(n_j^{(i)}\): population in stratum \(j\) of area \(i\)

Limitations

SMRs easy to calculate but may be misleading and unreliable in areas with small populations or rare diseases. Models enable to incorporate covariates & borrow information from neighboring areas to obtain smoothed relative risks

Areal models

Model to estimate disease relative risk \(\theta_i\) in areas \(i=1,\ldots,n\)

\[Y_i|\theta_i \sim Poisson(E_i \times \theta_i)\] \[\log(\theta_i) = \boldsymbol{z}_i \boldsymbol{\beta} + u_i + v_i\]

  • \(Y_i\) observed cases, \(E_i\) expected cases, \(\theta_i\) relative risk in area \(i\)

Fixed effects quantify the effects of the covariates on the disease risk

  • \(\boldsymbol{z}_i = (1, z_{i1}, \ldots, z_{ip})\) intercept and cov. \(\beta = (\beta_0, \beta_1, \ldots, \beta_p)'\) coeff.

Random effects represent residual variation not explained by the covariates

  • \(u_i\): structured spatial effect to account for spatial dependence between relative risks (close areas show more similar risk than not close areas)
  • \(v_i\): unstructured effect to account for independent noise

Spatial neighborhood matrix

library(spdep)
nb <- poly2nb(map)

## [[1]]
## [1] 21 28 67
## 
## [[2]]
## [1]  3  4 10 63 65

Inference using INLA

\[Y_i|\theta_i \sim Poisson(E_i \times \theta_i)\] \[\log(\theta_i) = \boldsymbol{z}_i \boldsymbol{\beta} + u_i + v_i\]

library(INLA)

formula <- Y ~ cov +
  f(idareau, model = "besag", graph = g, scale.model = TRUE) +
  f(idareav, model = "iid")

res <- inla(formula, family = "poisson", data = map@data,
  E = E, control.predictor = list(compute = TRUE))

# Posterior mean and 95% CI
map$RR <- res$summary.fitted.values[, "mean"]
map$LL <- res$summary.fitted.values[, "0.025quant"]
map$UL <- res$summary.fitted.values[, "0.975quant"]

R-INLA: http://www.r-inla.org/

Visualization

library(leaflet)

pal <- colorNumeric(palette = "YlOrRd", domain = map$SIR)

leaflet(map) %>% addTiles() %>%
addPolygons(color = "grey", weight = 1, fillColor = ~ pal(SIR)) %>%
addLegend(pal = pal, values = ~SIR, title = "SIR")

Tutorial

Modeling areal data (lung cancer risk in Pennsylvania, USA)

https://www.paulamoraga.com/book-spatial/disease-risk-modeling.html

Spatial neighborhood matrices

Neighbors based on contiguity

Neighbors based on contiguity assume that neighbors of a given area are other areas that share a common boundary. Neighbors can be of type Queen if a single shared boundary point meets the contiguity condition, or Rook if more than one shared point is required to meet the contiguity condition

Neighbors based on contiguity

# 49 neighborhoods of Columbus, Ohio, USA
library(spData)
library(sf)
map <- st_read(system.file("shapes/columbus.gpkg",
               package = "spData"), quiet = TRUE)
plot(st_geometry(map))

library(spdep)
nb <- poly2nb(map, queen = TRUE)
head(nb)
[[1]]
[1] 2 3

[[2]]
[1] 1 3 4

[[3]]
[1] 1 2 4 5

[[4]]
[1] 2 3 5 8

[[5]]
[1]  3  4  6  8  9 11 15 16

[[6]]
[1] 5 9

Neighbors based on contiguity

nb <- poly2nb(map)
id <- 20 # neighbors of area id 20
map$neighbors <- "other"; map$neighbors[id] <- "area"; map$neighbors[nb[[id]]] <- "neighbors"
plot(st_geometry(map), col = c("gray30", "gray", "white")[as.factor(map$neighbors)])

Neighbors based on k nearest neighbors

coo <- st_centroid(map); nb <- knn2nb(knearneigh(coo, k = 3)) # k number nearest neighbors
id <- 20 # neighbors of area id 20
map$neighbors <- "other"; map$neighbors[id] <- "area"; map$neighbors[nb[[id]]] <- "neighbors"
plot(st_geometry(map), col = c("gray30", "gray", "white")[as.factor(map$neighbors)])

Neighbors based on distance

nb <- dnearneigh(x = st_centroid(map), d1 = 0, d2 = 1)
id <- 20 # neighbors of area id 20
map$neighbors <- "other"; map$neighbors[id] <- "area"; map$neighbors[nb[[id]]] <- "neighbors"
plot(st_geometry(map), col = c("gray30", "gray", "white")[as.factor(map$neighbors)])

Neighbors of order k based on contiguity

Neighbors of order k based on contiguity

nb <- poly2nb(map, queen = TRUE)
nblags <- spdep::nblag(neighbours = nb, maxlag = 2)
par(mfrow = c(1, 2))
nb1 <- nblags[[1]]; nb2 <- nblags[[2]] # Neighbors of first and second order
id <- 20 # neighbors of area id 20
map$neighbors <- "other"; map$neighbors[id] <- "area"; map$neighbors[nb1[[id]]] <- "neighbors"
plot(st_geometry(map), col = c("gray30", "gray", "white")[as.factor(map$neighbors)])
map$neighbors <- "other"; map$neighbors[id] <- "area"; map$neighbors[nb2[[id]]] <- "neighbors"
plot(st_geometry(map), col = c("gray30", "gray", "white")[as.factor(map$neighbors)])

Neighborhood matrices

A neighborhood matrix \(W\) defines a neighborhood structure over the study region, and its elements can be viewed as weights. \(w_{ij}\) spatially connects areas \(i\) and \(j\) in some fashion. More weight is associated with areas closer to \(i\) than farther away from \(i\)

Neighborhood matrices

If neighbors are based on contiguity, we can construct a binary spatial matrix with \(w_{ij} = 1\) if regions \(i\) and \(j\) share a common boundary, and \(w_{ij} = 0\) otherwise. Customarily, \(w_{ii}\) is set to 0 for \(i = 1, \ldots, n\)

\[ \begin{matrix} & A & B & C & D & E & \text{Sum} \\ A & 0 & 1 & 1 & 1 & 0 & 3 \\ B & 1 & 0 & 1 & 1 & 1 & 4 \\ C & 1 & 1 & 0 & 1 & 0 & 3 \\ D & 1 & 1 & 1 & 0 & 1 & 4 \\ E & 0 & 1 & 0 & 1 & 0 & 2 \end{matrix} \]

Neighborhood matrices

Other definitions could be \(w_{ij} = 1\) for all \(i\) and \(j\) within a specified distance, \(w_{ij} = 1\) if \(j\) is one of the \(k\) nearest neighbors of \(i\), or \(w_{ij}\) = inverse distance between areas

We may also want to adjust for the total number of neighbors in each area and use a standardized matrix with entries \(w_{std,i,j} = w_{ij}/\sum_{j=1}^n w_{ij}\)

\[ \begin{matrix} & A & B & C & D & E & \text{Sum} \\ A & 0 & 1 & 1 & 1 & 0 & 3 \\ B & 1 & 0 & 1 & 1 & 1 & 4 \\ C & 1 & 1 & 0 & 1 & 0 & 3 \\ D & 1 & 1 & 1 & 0 & 1 & 4 \\ E & 0 & 1 & 0 & 1 & 0 & 2 \end{matrix} \]

\[ \begin{matrix} & A & B & C & D & E & \text{Sum} \\ A & 0 & 0.333 & 0.333 & 0.333 & 0 & 1 \\ B & 0.25 & 0 & 0.25 & 0.25 & 0.25 & 1 \\ C & 0.333 & 0.333 & 0 & 0.333 & 0 & 1 \\ D & 0.25 & 0.25 & 0.25 & 0 & 0.25 & 1 \\ E & 0 & 0.5 & 0 & 0.5 & 0 & 1 \end{matrix} \]

Spatial weights matrix based on a binary neighbor list

nb <- poly2nb(map, queen = TRUE)
nb[1:2]
[[1]]
[1] 2 3

[[2]]
[1] 1 3 4
# style B binary, W row standardized (1/n.neigh)
nbw <- spdep::nb2listw(nb, style = "W")
nbw$weights[1:2]
[[1]]
[1] 0.5 0.5

[[2]]
[1] 0.3333333 0.3333333 0.3333333
m1 <- listw2mat(nbw)
lattice::levelplot(t(m1))

m1[1:2, ]
          1   2         3         4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
1 0.0000000 0.5 0.5000000 0.0000000 0 0 0 0 0  0  0  0  0  0  0  0  0  0  0  0
2 0.3333333 0.0 0.3333333 0.3333333 0 0 0 0 0  0  0  0  0  0  0  0  0  0  0  0
  21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
2  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
  47 48 49
1  0  0  0
2  0  0  0

Exercise

Consider the following data of housing prices in Boston tracts and calculate the spatial weights matrix based on a binary neighbor list

library(spData)
library(sf)
library(mapview)
map <- st_read(system.file("shapes/boston_tracts.gpkg", package = "spData"), quiet = TRUE)
map$vble <- map$MEDV
mapview(map, zcol = "vble")

Exercise

nb <- poly2nb(map, queen = TRUE)
nb[1:2]
[[1]]
[1]   2   3   6   8 311 313 314 369

[[2]]
[1] 1 3 4 6
# style B binary, W row standardized (1/n.neigh)
nbw <- spdep::nb2listw(nb, style = "W")
nbw$weights[1:2]
[[1]]
[1] 0.125 0.125 0.125 0.125 0.125 0.125 0.125 0.125

[[2]]
[1] 0.25 0.25 0.25 0.25
m1 <- listw2mat(nbw)
m1[1:2, ]
     1     2     3    4 5     6 7     8 9 10 11 12 13 14 15 16 17 18 19 20 21
1 0.00 0.125 0.125 0.00 0 0.125 0 0.125 0  0  0  0  0  0  0  0  0  0  0  0  0
2 0.25 0.000 0.250 0.25 0 0.250 0 0.000 0  0  0  0  0  0  0  0  0  0  0  0  0
  22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
2  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
  48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
2  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
  74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
1  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
2  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0
  100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  309 310   311 312   313   314 315 316 317 318 319 320 321 322 323 324 325 326
1   0   0 0.125   0 0.125 0.125   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0 0.000   0 0.000 0.000   0   0   0   0   0   0   0   0   0   0   0   0
  327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  365 366 367 368   369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
1   0   0   0   0 0.125   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0 0.000   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  498 499 500 501 502 503 504 505 506
1   0   0   0   0   0   0   0   0   0
2   0   0   0   0   0   0   0   0   0

Spatial autocorrelation

Spatial autocorrelation

Spatial autocorrelation is used to describe the extent to which a variable is correlated with itself through space

Positive spatial autocorrelation occurs when observations with similar values are closer together (i.e., clustered). Negative spatial autocorrelation occurs when observations with dissimilar values are closer together (i.e., dispersed)

Global Moran’s \(I\)

Moran’s \(I\) is used to assess spatial autocorrelation in areal data. It summarizes the degree to which similar observations tend to occur near each other over the study region

\[I = \frac{n \sum_i \sum_j w_{ij}(Y_i - \bar Y)(Y_j - \bar Y)} {(\sum_{i \neq j} w_{ij}) \sum_i (Y_i - \bar Y)^2}\]

Global Moran’s \(I\)

\[I = \frac{n \sum_i \sum_j w_{ij}(Y_i - \bar Y)(Y_j - \bar Y)} {(\sum_{i \neq j} w_{ij}) \sum_i (Y_i - \bar Y)^2}\]

\(E[I] = \frac{-1}{n-1},\ \ \ \ \ Var[I] = \frac{n^2(n-1)S_1 - n(n-1)S_2 - 2 S_0^2}{(n+1)(n-1)^2 S_0^2}\)

\(S_0 = \sum_{i \neq j} w_{ij},\ S_1= \frac{1}{2}\sum_{i\neq j} (w_{ij}+w_{ji})^2\mbox{ and } S_2 = \sum_k \left(\sum_j w_{kj}+\sum_i w_{ik}\right)^2\)

  • Moran’s \(I\) \(>\) \(E[I] = -1/(n-1)\) indicate positive spatial autocorrelation or clustering, neighboring regions tend to have similar values

  • Moran’s \(I\) \(<\) \(E[I]\) indicate negative spatial autocorrelation or dispersion, regions that are close to one another tend to have different values

  • Moran’s \(I\) around \(E[I]\) indicate randomness, absence of spatial pattern

Spatial autocorrelation

https://www.paulamoraga.com/book-spatial/spatial-autocorrelation.html

library(spData)
library(sf)
library(mapview)
map <- st_read(system.file("shapes/boston_tracts.gpkg", package = "spData"), quiet = TRUE)
map$vble <- map$MEDV
mapview(map, zcol = "vble")

Global Moran’s \(I\)

# Neighbors
nb <- spdep::poly2nb(map, queen = TRUE) # queen shares point or border
nbw <- nb2listw(nb, style = "W")
# Global Moran's I
gmoran <- moran.test(map$vble, nbw, alternative = "two.sided")
gmoran

    Moran I test under randomisation

data:  map$vble  
weights: nbw    

Moran I statistic standard deviate = 23.35, p-value < 2.2e-16
alternative hypothesis: two.sided
sample estimates:
Moran I statistic       Expectation          Variance 
     0.6266753872     -0.0019801980      0.0007248686 


Moran’s \(I\) \(>\) \(E[I] = -1/(n-1)\) indicate positive spatial autocorrelation or clustering, neighboring regions tend to have similar values

Assessing significance

\(H_0: I = E[I]\) no spatial autocorrelation
\(H_1: I \neq E[I]\) spatial autocorrelation


When the number of regions is sufficiently large, \(I\) has normal distribution and we can assess whether a pattern deviates significantly from a random pattern by comparing the z-score \(z = \frac{I-E(I)}{Var(I)^{1/2}}\) to the standard normal distribution


An alternative approach to assess significance is Monte Carlo randomization. Create random patterns by reassigning the observed values among the areas and calculate Moran’s \(I\) for each pattern, obtaining a randomization distribution for Moran’s \(I\). If the observed Moran’s \(I\) lies in the tails of this distribution, the assumption of no spatial autocorrelation is rejected

Assessing significance

\(H_0: I = E[I]\) no spatial autocorrelation
\(H_1: I \neq E[I]\) positive or negative spatial autocorrelation

Assessing significance with Normal(0, 1)

  1. State the null and alternative hypotheses:
    \(H_0: I = E[I]\) no spatial autocorrelation
    \(H_1: I \neq E[I]\) spatial autocorrelation

  2. Choose the significance level \(\alpha\) we are willing to tolerate, which represents the maximum value for the probability of incorrectly rejecting the null hypothesis when it is true (usually \(\alpha = 0.05\)).

  3. Calculate the test statistic:
    \[z = \frac{I-E(I)}{Var(I)^{1/2}}.\]

  4. Find the p-value for the observed data by comparing the z-score to the standard normal distribution or via Monte Carlo randomization. The p-value is the probability of obtaining a test statistic as extreme as or more extreme than the one observed test statistic in the direction of the alternative hypothesis, assuming the null hypothesis is true.

  5. Make one of these two decisions and state a conclusion:
    If p-value \(< \alpha\), we reject the null hypothesis. We conclude data provide evidence for the alternative hypothesis.
    If p-value \(\geq \alpha\), we fail to reject the null hypothesis. The data do not provide evidence for the alternative hypothesis.

Assessing significance with Normal(0, 1)

\(H_0: I = E[I]\) no spatial autocorrelation
\(H_1: I \neq E[I]\) positive or negative spatial autocorrelation

# Neighbors
# queen shares point or border
nb <- spdep::poly2nb(map, queen = TRUE)
nbw <- nb2listw(nb, style = "W")
# Global Moran's I
gmoran <- moran.test(map$vble, nbw, alternative = "two.sided")
gmoran

    Moran I test under randomisation

data:  map$vble  
weights: nbw    

Moran I statistic standard deviate = 23.35, p-value < 2.2e-16
alternative hypothesis: two.sided
sample estimates:
Moran I statistic       Expectation          Variance 
     0.6266753872     -0.0019801980      0.0007248686 
gmoran[["estimate"]][["Moran I statistic"]]
[1] 0.6266754
gmoran[["statistic"]] # z-score
Moran I statistic standard deviate 
                           23.3498 
gmoran[["p.value"]] # p-value
[1] 1.384667e-120

p-value \(<\) significance level 0.05. Then, we reject the null hypothesis and conclude there is evidence for spatial autocorrelation

Assessing significance with Monte Carlo

\(H_0: I = E[I]\) no spatial autocorrelation
\(H_1: I \neq E[I]\) positive or negative spatial autocorrelation

Create random patterns by reassigning values among areas and calculate Moran’s \(I\) for each pattern. p-value is proportion of values as extreme or more extreme than the statistic observed in the direction of alternative hypothesis. p-value \(<\) 0.05 indicating data presents spatial autocorrelation

(gmoranMC <- moran.mc(map$vble, nbw, nsim = 999,
                      alternative = "two.sided"))

    Monte-Carlo simulation of Moran I

data:  map$vble 
weights: nbw  
number of simulations + 1: 1000 

statistic = 0.62668, observed rank = 1000, p-value < 2.2e-16
alternative hypothesis: two.sided
# Moran's I for simulated patterns
hist(gmoranMC$res)
# Red line Moran's I for real data
abline(v = gmoranMC$statistic, col = "red")

Assessing significance

\(H_0: I \leq E[I]\) negative spatial autocorrelation or no spatial autocorrelation
\(H_1: I > E[I]\) positive spatial autocorrelation

Assessing significance with Normal(0, 1)

\(H_0: I \leq E[I]\) negative spatial autocorrelation or no spatial autocorrelation
\(H_1: I > E[I]\) positive spatial autocorrelation

# Neighbors
# queen shares point or border
nb <- spdep::poly2nb(map, queen = TRUE)
nbw <- nb2listw(nb, style = "W")
# Global Moran's I
gmoran <- moran.test(map$vble, nbw, alternative = "greater")
gmoran

    Moran I test under randomisation

data:  map$vble  
weights: nbw    

Moran I statistic standard deviate = 23.35, p-value < 2.2e-16
alternative hypothesis: greater
sample estimates:
Moran I statistic       Expectation          Variance 
     0.6266753872     -0.0019801980      0.0007248686 
gmoran[["estimate"]][["Moran I statistic"]]
[1] 0.6266754
gmoran[["statistic"]] # z-score
Moran I statistic standard deviate 
                           23.3498 
gmoran[["p.value"]] # p-value
[1] 6.923334e-121

p-value \(<\) significance level 0.05. Then, we reject the null hypothesis and conclude there is evidence for positive spatial autocorrelation

Assessing significance with Monte Carlo

\(H_0: I \leq E[I]\) negative spatial autocorrelation or no spatial autocorrelation
\(H_1: I > E[I]\) positive spatial autocorrelation

Create random patterns by reassigning values among areas and calculate Moran’s \(I\) for each pattern. p-value is proportion of values as extreme or more extreme than the statistic observed in the direction of alternative hypothesis. p-value \(<\) 0.05 indicating data presents positive spatial autocorrelation

(gmoranMC <- moran.mc(map$vble, nbw, nsim = 999))

    Monte-Carlo simulation of Moran I

data:  map$vble 
weights: nbw  
number of simulations + 1: 1000 

statistic = 0.62668, observed rank = 1000, p-value = 0.001
alternative hypothesis: greater
# Moran's I for simulated patterns
hist(gmoranMC$res)
# Red line Moran's I for real data
abline(v = gmoranMC$statistic, col = "red")

Moran’s \(I\) scatterplot

Moran’s \(I\) scatterplot can be used to visualize the spatial autocorrelation in the data. It displays observations of each area against its spatially lagged values

The spatially lagged value for a given area is calculated as a weighted average of the neighboring values for that area

\[(Y_i,\ \sum_{j=1}^n w_{ij}Y_j)\]

moran.plot(map$vble, nbw)

Exercise

Reproduce the Moran’s \(I\) scatterplot Spatial lagged variable for area \(i = \sum_{j=1}^n w_{ij}Y_j\) (sum(neighmat[i, ]*map$vble))
Spatial lagged variable can be obtained with spdep::lag.listw() or apply(neighmat %*% map$vble, 1, sum)

nb <- poly2nb(map, queen = TRUE)
nbw <- nb2listw(nb, style = "W") # style = "W" is row standardized
# spatial lagged variable
vblelag <- lag.listw(nbw, map$vble) #  spatial weights of areas and values
res <- lm(vblelag ~ map$vble)
plot(map$vble, vblelag, xlab = "vble", ylab = "Spatial lag of vble")
abline(res$coefficients[1], res$coefficients[2])
abline(h = mean(vblelag), lty = 2); abline(v = mean(map$vble), lty = 2)

Local Moran’s \(I\)

Global Moran’s \(I\) assesses spatial autocorrelation for the whole study region. We can also provide a local measure of similarity between each area’s value and those of nearby areas

Local Indicators of Spatial Association (LISA) provide indicate the extent of significant spatial clustering of similar values around each observation

Local Moran’s \(I\)

\[\mbox{For the $i$th region,}\ \ \ I_i = \frac{n (Y_i - \bar Y)}{\sum_j (Y_j - \bar Y)^2} \sum_j w_{ij}(Y_j - \bar Y)\]

Global Moran’s \(I\) is proportional to sum of local Moran’s \(I\) for all regions

\[I = \frac{1}{\sum_{i \neq j} w_{ij}}\sum_i I_i\]

  • A high value for \(I_i\) suggests that the area is surrounded by areas with similar values. Such an area is part of a cluster of high observations, low observations, or moderate observations.

  • A low value for \(I_i\) indicates that the area is surrounded by areas with dissimilar values. Such an area is an outlier indicating that the observation of area \(i\) is different from most or all of the observations of its neighbors.

Local Moran’s \(I\)

\(H_0\): no or negative spatial autocorrelation, \(H_1\): positive spatial autocorrelation

lmoran <- localmoran(map$vble, nbw, alternative = "greater")
head(lmoran)
             Ii          E.Ii       Var.Ii        Z.Ii Pr(z > E(Ii))
1 -0.3457508492 -5.254157e-04 3.275376e-02 -1.90753363  9.717742e-01
2  0.0175875407 -1.626873e-05 2.045711e-03  0.38921049  3.485602e-01
3  0.0123379633 -6.557001e-07 4.089699e-05  1.92939381  2.684100e-02
4 -0.0001654033 -1.059064e-07 1.331742e-05 -0.04529559  5.180641e-01
5  0.3591628595 -1.427815e-04 7.898947e-03  4.04277384  2.641128e-05
6  0.0545610965 -1.625936e-04 1.357382e-02  0.46970410  3.192832e-01
  • Ii: Local Moran’s \(I\) statistic for each area
  • E.Ii: Expectation Local Moran’s \(I\) statistic
  • Var.Ii: Variance Local Moran’s \(I\) statistic
  • Z.Ii: z-score
  • Pr(z > E(Ii)), Pr(z < E(Ii)) or Pr(z != E(Ii)): p-value for an alternative hypothesis greater, less or two.sided

Local Moran’s \(I\)

map$lmI <- lmoran[, "Ii"] # local Moran's I
map$lmZ <- lmoran[, "Z.Ii"] # z-scores
map$lmp <- lmoran[, "Pr(z > E(Ii))"] # p-values corresponding to alternative greater
library(tmap)
p1 <- tm_shape(map) + tm_polygons(col = "vble", title = "vble", style = "quantile") + tm_layout(legend.outside = TRUE)
p2 <- tm_shape(map) + tm_polygons(col = "lmI", title = "Local Moran's I", style = "quantile") + tm_layout(legend.outside = TRUE)
p3 <- tm_shape(map) + tm_polygons(col = "lmZ", title = "Z-score", breaks = c(-Inf, 1.65, Inf)) + tm_layout(legend.outside = TRUE)
p4 <- tm_shape(map) + tm_polygons(col = "lmp", title = "p-value", breaks = c(-Inf, 0.05, Inf)) + tm_layout(legend.outside = TRUE)
tmap_arrange(p1, p2, p3, p4)

Local Moran’s \(I\)

alternative = "greater"

\(H_0\): no or negative spatial autocorrelation

\(H_1\): positive spatial autocorrelation

z-score values greater than 1.65 indicate positive spatial autocorrelation

tm_shape(map) + tm_polygons(col = "lmZ", title = "Local Moran's I", style = "fixed",
breaks = c(-Inf, 1.65, Inf), labels = c("No or Negative SAC", "Positive SAC"),
palette =  c("white", "red")) + tm_layout(legend.outside = TRUE)

Local Moran’s \(I\)

alternative = "two.sided"

\(H_0\): no spatial autocorrelation

\(H_1\): positive or negative spatial autocorrelation

z-score values lower than -1.96 indicate negative spatial autocorrelation, and z-score values greater than 1.96 indicate positive spatial autocorrelation

tm_shape(map) + tm_polygons(col = "lmZ", title = "Local Moran's I", style = "fixed",
breaks = c(-Inf, -1.96, 1.96, Inf), labels = c("Negative SAC", "No SAC", "Positive SAC"),
palette =  c("blue", "white", "red")) + tm_layout(legend.outside = TRUE)

Clusters

Local Moran’s \(I\) allows us to identify clusters of the following types:

  • High-High: areas of high values with neighbors of high values
  • High-Low: areas of high values with neighbors of low values
  • Low-High: areas of low values with neighbors of high values
  • Low-Low: areas of low values with neighbors of low values
nb <- spdep::poly2nb(map, queen = TRUE)
nbw <- nb2listw(nb, style = "W")
lmoran <- localmoran(map$vble, nbw, alternative = "two.sided")
head(lmoran)
             Ii          E.Ii       Var.Ii        Z.Ii Pr(z != E(Ii))
1 -0.3457508492 -5.254157e-04 3.275376e-02 -1.90753363   5.645152e-02
2  0.0175875407 -1.626873e-05 2.045711e-03  0.38921049   6.971204e-01
3  0.0123379633 -6.557001e-07 4.089699e-05  1.92939381   5.368199e-02
4 -0.0001654033 -1.059064e-07 1.331742e-05 -0.04529559   9.638717e-01
5  0.3591628595 -1.427815e-04 7.898947e-03  4.04277384   5.282257e-05
6  0.0545610965 -1.625936e-04 1.357382e-02  0.46970410   6.385664e-01
map$lmp <- lmoran[, 5] # p-values are in column 5

Clusters

We identify the clusters of each type by using the information from the Moran’s \(I\) scatterplot showing the scaled values against its spatially lagged values

mp <- moran.plot(as.vector(scale(map$vble)), nbw)

Clusters

We identify cluster types by using the quadrants of the scaled values (mp$x) and their spatially lagged values (mp$wx), and p-values obtained with the local Moran’s \(I\) for each area (map$lmp). Areas with significant local Moran’s \(I\) are:

  • High-high if both the value and its spatially lagged value are positive
  • Low-low if both the value and its spatially lagged value are negative
  • High-low if the the value is positive and the spatially lagged value negative
  • Low-high is the value is negative and the spatially lagged value positive
map$quadrant <- NA
# high-high
map[(mp$x >= 0 & mp$wx >= 0) & (map$lmp <= 0.05), "quadrant"]<- 1
# low-low
map[(mp$x <= 0 & mp$wx <= 0) & (map$lmp <= 0.05), "quadrant"]<- 2
# high-low
map[(mp$x >= 0 & mp$wx <= 0) & (map$lmp <= 0.05), "quadrant"]<- 3
# low-high
map[(mp$x <= 0 & mp$wx >= 0) & (map$lmp <= 0.05), "quadrant"]<- 4
# non-significant
map[(map$lmp > 0.05), "quadrant"] <- 5

Clusters

tm_shape(map) + tm_fill(col = "quadrant", title = "", breaks = c(1, 2, 3, 4, 5, 6),
                        palette =  c("red", "blue", "lightpink", "skyblue2", "white"),
                        labels = c("High-High", "Low-Low", "High-Low", "Low-High", "Non-significant")) +
tm_legend(text.size = 1)  + tm_borders(alpha = 0.5) + tm_layout(frame = FALSE, title = "Clusters")  +
  tm_layout(legend.outside = TRUE)

Exercise

Identify cluster types of household income in Ohio

# https://jakubnowosad.com/spData/reference/columbus.html?utm_source=chatgpt.com
# x and y coordinates (in arbitrary digitizing units, not polygon coordinates)
library(spData); library(sf); library(mapview)
map <- st_read(system.file("shapes/columbus.gpkg", package = "spData"), quiet = TRUE)
# Assign CRS so mapview can plot it. https://epsg.io/
# It will not be able to transform to geographical CRS 4326 since coordinates are in arbitrary units
st_crs(map) <- "EPSG:3631"
map$vble <- map$INC
mapview(map, zcol = "vble")

Geostatistical data

Geostatistical data

Moraga, et al., Parasites & Vectors, 2015

Geostatistical models

Models to predict prevalence at unsampled locations

\[Y_i|P(\boldsymbol{x}_i)\sim \mbox{Binomial} (n_i, P(\boldsymbol{x}_i))\] \[\mbox{logit}(P(\boldsymbol{x}_i)) = \boldsymbol{z}_i \boldsymbol{\beta} + S(\boldsymbol{x}_i) + u_i\]

\(Y_i\) number people positive, \(n_i\) number people tested, \(P(\boldsymbol{x}_i)\) prevalence at \(\boldsymbol{x}_i\)

Covariates based on characteristics known to affect disease transmission (temperature, precipitation, vegetation, elevation, land cover, population density, etc.)

Random effects model residual variation not explained by covariates

Inference using INLA and SPDE

Integrated nested Laplace approximations (INLA) is a computational approach to perform approximate Bayesian inference in latent Gaussian models

In the Stochastic partial differential equation (SPDE) approach, the continuously indexed Gaussian random field \(S\) is represented as a discretely indexed Gaussian Markov random field (GMRF) by means of a finite basis function defined on a triangulation of the study region

\[S(\boldsymbol{x}) = \sum_{g=1}^G \psi_g(\boldsymbol{x}) S_g\]

\(\psi_g(\cdot)\) piecewise polynomial basis functions on each triangle
\(\{S_g \}\) zero-mean Gaussian distributed
\(G\) number of vertices in triangulation

Projection matrix

\(S(\boldsymbol{x})\) weighted average of the GMRF values at the vertices of the triangle containing the point. Weights = barycentric coordinates

\[S(\boldsymbol{x}) \approx \frac{T_{1}}{T}S_1 + \frac{T_{2}}{T}S_2 + \frac{T_{3}}{T}S_3\]

\(T_1, T_2, T_3\) areas subtriangles formed by \(\boldsymbol{x}\) and vertices. \(T\) area whole triangle

Projection matrix

\[S(\boldsymbol{x}_i) \approx \sum_{g=1}^G A_{ig} S_g\] \(A\) projection matrix that maps GMRF from observations to triangulation nodes

Row \(i\) corresponding to observation \(i\) has possibly three non-zero values at columns that represent the vertices of the triangle containing the point
(non-zero values are equal to the barycentric coordinates)

\[A = \begin{bmatrix} A_{11} & A_{12} & A_{13} & \dots & A_{1G} \\ A_{21} & A_{22} & A_{23} & \dots & A_{2G} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ A_{n1} & A_{n2} & A_{n3} & \dots & A_{nG} \end{bmatrix} = \begin{bmatrix} 1 & 0 & 0 & \dots & 0 \\ 0 & 0 & 1 & \dots & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 0.2 & 0.2 & 0 & \dots & 0.6 \end{bmatrix}\]

Tutorial

Modeling geostatistical data (air pollution in USA)

https://www.paulamoraga.com/book-spatial/sec-geostatisticaldataSPDE.html

Tutorial

Modeling geostatistical data (malaria prevalence in The Gambia)

https://www.paulamoraga.com/book-geospatial/sec-geostatisticaldataexamplespatial.html

Point patterns

Point patterns


Assume point pattern \(\{s_i: i=1, \ldots, n\}\) has been generated as a realization of a point process \(Z = \{Z(s): s \in \mathbb{R}^2\}\)


A point process model can be used to estimate the intensity of events, identify patterns in the distribution of the observed locations, and learn about the correlation between the locations and spatial covariates

Intensity of the process

Intensity of the process: mean number of events per unit area at location \(s\) \[\lambda(s) = lim_{|ds|\rightarrow 0} \frac{E[N(ds)]}{|ds|}\]

library(spatstat)
plot(X)
den <- density(x = X, sigma = 10)
plot(den, main = "Intensity")
contour(den, add = TRUE)

Complete Spatial Randomness

Point processes are stochastic models that describe the locations of events of interest and possibly some additional information such as marks that inform about different types of events (e.g., cases and controls)

Point processes provide models for point patterns, with complete spatial randomness (CSR) being the simplest theoretical model

CSR helps differentiate between regular and clustered patterns

Homogeneous Poisson process (CSR)

Simple model that assumes events are equally likely to occur at any location within the study area, independent of the locations of other events

  1. the number of events in any region \(A\) follows a Poisson distribution with mean \(\lambda |A|\), where \(\lambda\) is a constant value denoting the intensity and \(|A|\) is the area of \(A\)
  2. given that there are \(n\) events inside \(A\), the locations of these events are independent and uniformly distributed in \(A\)

Inhomogeneous Poisson process

In homogeneous Poisson processes, the intensity is constant (\(\lambda(s)= \lambda,\ \ \forall s\)), whereas in inhomogeneous Poisson processes, the intensity varies in space.

  1. the number of events in any region \(A\) follows a Poisson distribution with mean \(\mu(A) = \int_A \lambda(s)ds\)
  2. given that there are \(n\) events inside \(A\), the locations of these events are independent with probability density function proportional to the intensity \(\lambda(\cdot)\)

Log-Gaussian Cox process (LGCP)

Log-Gaussian Cox processes (LGCP) are typically used to model phenomena that are environmentally driven

A LGCP is a Poisson process with a varying intensity which is itself a stochastic process of the form \[\Lambda(s) = exp(\eta(s)),\] where \(\eta = \{\eta(s): s \in \mathbb{R}^2\}\) is spatial Gaussian process

Conditional on \(\Lambda(s)\), the point process is a Poisson process with intensity \(\Lambda(s)\) 1. the number of events in any region \(A\) is distributed as \(Poisson(\int_A \Lambda(s)ds)\) 2. the locations of events are an independent random sample with probability density proportional to \(\Lambda(s)\)

Fitting a log-Gaussian Cox process model

Assume point pattern \(\{s_i: i=1, \ldots, n\}\) has been generated as a realization of a LGCP with intensity \(\Lambda(s)= exp(\eta(s))\)

A LGCP can be fitted by discretizing the study region into a grid with \(n_1 \times n_2\) cells \(\{s_{ij}: i=1,\ldots,n_1, j=1,\ldots,n_2\}\). \(|s_{ij}|\) area of cell \(s_{ij}\)

Mean number of events in cell \(s_{ij}\):

\(\Lambda_{ij}=\int_{s_{ij}} exp(\eta(s))ds \approx |s_{ij}| exp(\eta_{ij})\)

\(y_{ij}|\eta_{ij} \sim Poisson(|s_{ij}| exp(\eta_{ij}))\)

\(\eta_{ij} = \beta_0 + \beta_1 \times cov(s_{ij}) + f_s(s_{ij}) + f_u(s_{ij})\)

  • \(y_{ij}\) observed number of locations in grid cell \(s_{ij}\)
  • \(\beta_0\) intercept, \(cov(s_{ij})\) covariate at \(s_{ij}\), \(\beta_1\) coefficient of \(cov(s_{ij})\)
  • \(f_s()\) spatially structured random effect (CAR model on a regular lattice)
  • \(f_u()\) unstructured random effect

Tutorial

Modeling point patterns (sloths occurrence in Costa Rica)

http://www.paulamoraga.com/tutorial-point-patterns

spocc package: https://docs.ropensci.org/spocc/

Reproducible documents with R Markdown

R Markdown

R Markdown can be used to turn our analysis into fully reproducible documents that can be shared with others. Output formats include HTML, PDF or Word. An R Markdown file is written with Markdown syntax with embedded R code, and can include narrative text, tables and visualizations

http://www.paulamoraga.com/book-geospatial/sec-rmarkdown.html

YAML header

---
title: "An R Markdown document"
author: "Paula Moraga"
date: "1 July 2019"
output: pdf_document
---

Markdown syntax

**bold text**

bold text

*italic text*

italic text


- unordered item
- unordered item
  • unordered item
  • unordered item
1. first item
2. second item
  1. first item
  2. second item
# First-level header
## Second-level header
### Third-level header

First-level header

Second-level header

Third-level header


$$\int_0^\infty e^{-x^2} dx=\frac{\sqrt{\pi}}{2}$$

\[\int_0^\infty e^{-x^2} dx=\frac{\sqrt{\pi}}{2}\]

R code chunks

```{r, warning = FALSE}
# R code to be executed
```

Options:

  • echo=FALSE code will not be shown in the document, but it will run and the output will be displayed in the document
  • eval=FALSE code will not run, but it will be shown in the document
  • include=FALSE code will run, but neither the code nor the output will be included in the document
  • results='hide' output will not be shown, but the code will run and will be displayed in the document
  • cache=TRUE code chunk is not executed if it has been executed before and nothing in the code chunk has changed since then
  • error=FALSE, warning=FALSE, message=FALSE supress errors, warnings or messages

R Markdown

Interactive dashboards with flexdashboard

Dashboards with flexdashboard

The R package flexdashboard uses R Markdown to publish a group of related data visualizations as a dashboard

http://www.paulamoraga.com/book-geospatial/sec-flexdashboard.html

Layout

Dashboards with flexdashboard

Tutorial

Interactive dashboards with flexdashboard to communicate results

https://www.paulamoraga.com/book-geospatial/sec-flexdashboard

Example (lung cancer risk in Pennsylvania, USA)

http://www.paulamoraga.com/tutorial-flexdashboard-example

Shiny web applications

Shiny web applications

Shiny is a web application framework for R that enables to build interactive web applications


Examples: Gallery, Example 1, Example 2


Resources: Posit tutorial, Mastering Shiny Book, Cheatsheet

http://www.paulamoraga.com/book-geospatial/sec-shiny.html

Examples

Gallery

SpatialEpiApp

SpatialEpiApp is a Shiny app for disease risk estimation, cluster detection, and interactive visualization

  • Risk estimates by fitting Bayesian models with INLA
  • Detection of clusters by using the scan statistics in SaTScan
devtools::install_github("Paula-Moraga/SpatialEpiApp")
library(SpatialEpiApp); run_app()

Structure of a Shiny App

A Shiny app can be built by creating a directory (called, for example, appdir) that contains an R file (called, for example, app.R) with three components:

  • ui user interface object which controls layout and appearance of the app
  • server() function with instructions to build objects displayed in the ui
  • call to shinyApp() that creates the Shiny app from the ui/server pair
# define user interface object
ui <- fluidPage( )
# define server() function
server <- function(input, output){ }
# call to shinyApp() which returns the Shiny app
shinyApp(ui = ui, server = server)

Save app.R inside directory appdir. Launch the app:

shiny::runApp("appdir_path")

Directory appdir can also contain data or other R scripts needed by the app. We can also write two separate files ui.R and server.R. This permits an easier management of the code for large apps

Inputs

Outputs

Plots, tables, texts, images

Inputs, outputs and reactivity

Inputs: we can interact with the app by modifying their values
Outputs: objects we want to show in the app

ui <- fluidPage(
  *Input(inputId = myinput, label = mylabel, ...)
  *Output(outputId = myoutput, ...)
)
server <- function(input, output){
  output$myoutput <- render*({
    # code to build the output.
    # If it uses an input value (input$myinput),
    # the output will be rebuilt whenever the input value changes
  })}

We can include inputs by writing an *Input() function in the ui
(e.g., textInput(), dateRangeInput(), fileInput())

Outputs can be created by writing an *Output() function in the ui, and the R code to build the output inside a render*() function in server() (e.g., textOutput() and renderText(), tableOutput() and renderTable())

fluidPage() creates a display that automatically adjusts the app to the dimensions of the browser window

Inputs, outputs and reactivity

https://shiny.posit.co/r/gallery/start-simple/single-file-shiny-app/

Sharing a Shiny app

Option 1: Share R scripts with other users

  • users need R
library(shiny)
runApp("appdir_path")

Option 2: Host app as a web page at its own URL

  • users do not need R
  • app can be navigated through the internet with a web browser
  • host apps on own servers or using one of the ways Posit offers such as shinyapps.io and Shiny Server

Example: https://paulamoraga.shinyapps.io/spatialepiapp/

Shiny practical 1

Inputs, outputs and reactivity

Inputs: we can interact with the app by modifying their values
Outputs: objects we want to show in the app

ui <- fluidPage(
  *Input(inputId = myinput, label = mylabel, ...)
  *Output(outputId = myoutput, ...)
)
server <- function(input, output){
  output$myoutput <- render*({
    # code to build the output.
    # If it uses an input value (input$myinput),
    # the output will be rebuilt whenever the input value changes
  })}

We can include inputs by writing an *Input() function in the ui
(e.g., textInput(), dateRangeInput(), fileInput())

Outputs can be created by writing an *Output() function in the ui, and the R code to build the output inside a render*() function in server() (e.g., textOutput() and renderText(), tableOutput() and renderTable())

fluidPage() creates a display that automatically adjusts the app to the dimensions of the browser window

Example

Shiny app that greets the user by name

ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText({
    - COMPLETE -
  })
}

shinyApp(ui, server)

Example

Shiny app that greets the user by name

ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText({
    paste0("Hello ", input$name, "!")
  })
}

shinyApp(ui, server)

Example

Build an app that allows the user to set a number x between 1 and 50, and displays the result of multiplying this number by 5.

ui <- fluidPage(
  sliderInput("x", label = "If x is", min = 1, max = 50, value = 30),
  textOutput("product")
)

server <- function(input, output, session) {
  output$product <- renderText({ 
    - COMPLETE -
  })
}

shinyApp(ui, server)

Example

Build an app that allows the user to set a number x between 1 and 50, and displays the result of multiplying this number by 5.

ui <- fluidPage(
  sliderInput("x", label = "If x is", min = 1, max = 50, value = 30),
  textOutput("product")
)

server <- function(input, output, session) {
  output$product <- renderText({ 
    paste(input$x, "x 5 is", input$x * 5)
  })
}

shinyApp(ui, server)

Shiny widgets

Gallery: https://shiny.posit.co/r/gallery/widgets/widget-gallery/

List of inputs and outputs: https://shiny.posit.co/r/reference/shiny/latest/


Exercise

Build a Shiny app containing the following inputs and outputs:

  • Slider input with label "Select dates:", minimum date "2020-01-01", maximum date "2020-12-31", and initial value "2020-01-31".

  • Slider input to select a value between 0 and 100 where the interval between each selectable value on the slider is 5. Add animation so when the user presses play the input widget scrolls through automatically.

  • Output with a text showing the input values selected.

Exercise

library(shiny)

ui <- fluidPage(
  sliderInput("date", "Select date:",
              min = as.Date("2020-01-01"),
              max = as.Date("2020-12-31"),
              value = as.Date("2020-01-31")),
  sliderInput("number", "Select a number:",
              min = 0, max = 100, value = 0,
              step = 5, animate = TRUE),
  textOutput("inputvaluesselected")
)

server <- function(input, output, session) {
  
  output$inputvaluesselected <- renderText({
    paste("Date", input$date, ",",
          "number", input$number, ".")
  })

}

shinyApp(ui, server)

Reactive expressions

Book chapter 3

Reactive expressions take reactive values or other reactive expressions, and return an object to be used in the app

ui <- fluidPage(
  textInput("name", "What's your name?"),
  textOutput("greeting")
)

server <- function(input, output, session) {
  
  string <- reactive({paste0("Hello ", input$name, "!")})
  
  output$greeting <- renderText(string())
}

shinyApp(ui, server)

Reactive expressions

Shiny tutorial

Fibonacci sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …

# Function to calculate nth number in Fibonacci sequence
fib <- function(n){ifelse(n<3, 1, fib(n-1)+fib(n-2))}

ui <- fluidPage(
  numericInput("n", "Enter number", value = 1),
  textOutput("nthvalue"),
  textOutput("nthvalueInv")  
)

server <- function(input, output) {
  
  # time consuming calculation that we only want to do once
  # do calculation in a reactive expression
  currentFib <- reactive({fib(as.numeric(input$n))})
  
  output$nthvalue <- renderText({currentFib()})
  output$nthvalueInv <- renderText({1/currentFib()})
}

shinyApp(ui, server)

Observers

Observers, like reactive expressions, can take reactive values and reactive expressions. However, they do not return any values. Instead of returning values they have side effects

ui <- fluidPage(
  sliderInput("controller", "Controller", 0, 20, 10),
  textInput("inText", "Input text"),
  textInput("inText2", "Input text 2")
)

server <- function(input, output, session) {
  observe({
    # We use input$controller multiple times so we save it as x
    x <- input$controller
    
    # changes the value of input$inText
    updateTextInput(session, "inText", value = paste("New text", x))
    
    # changes the value and label of input$inText2
    updateTextInput(session, "inText2",
                    label = paste("New label", x),
                    value = paste("New text", x))
  })
}
shinyApp(ui, server)

Events

Shiny reference

Sometimes we may want to wait for a specific action to be taken from the user, like clicking an actionButton() or an actionLink(), before calculating an expression or taking an action

An event is a reactive value or expression that is used to trigger other calculations in this way

observeEvent() and eventReactive() can be used for event handling:

  • observeEvent() to perform an action in response to an event
  • eventReactive() to create a calculated value that only updates in response to an event

Events

Shiny reference

ui <- fluidPage(
  numericInput("number", "Value", 5),
  actionButton("button", "Show"),
  tableOutput("table")
)

server <- function(input, output) {
  # observeEvent() and eventReactive()
  # take a dependency on input$button, but
  # not on any of the stuff inside the functions
 
  # Take an action every time button is pressed 
  # Here, print a message to the console
  observeEvent(input$button, {cat("Showing",input$number,"rows\n")})
  
  # Calculate df every time button is pressed
  df <- eventReactive(input$button, {head(cars, input$number)})
  
  output$table <- renderTable({df()})
}

shinyApp(ui, server)

Uploads

Book chapter 9

ui <- fluidPage(
  fileInput("upload", label = "Select file",
            buttonLabel = "Upload...", multiple = TRUE),
  tableOutput("files")
)
server <- function(input, output, session) {
  output$files <- renderTable(input$upload)
}
shinyApp(ui, server)

fileInput() returns a data frame with four columns: name, size, type, datapath

Uploads

input$upload initialized to NULL on page load. Need req(input$upload) to make sure code waits until first file is uploaded

accept argument limits the possible inputs. But it is only a suggestion to browser not always enforced. Good practice validate (tools::file_ext())

ui <- fluidPage(
fileInput("upload", "Select file", accept = c(".csv", ".tsv")),
numericInput("n", "Rows", value = 5, min = 1, step = 1),
tableOutput("head")
)

server <- function(input, output, session) {
  data <- reactive({
    req(input$upload)
    ext <- tools::file_ext(input$upload$name)
    switch(ext, # file extension excluding leading dot
      csv = vroom::vroom(input$upload$datapath, delim = ","),
      tsv = vroom::vroom(input$upload$datapath, delim = "\t"),
      validate("Invalid file; Please upload a .csv or .tsv file"))
  })
  output$head <- renderTable({head(data(), input$n)})
}

shinyApp(ui, server)

Downloads

In ui, downloadButton(id) or downloadLink(id) so user can click to download a file. In server(), downloadHandler() with two fn arguments

ui <- fluidPage(
  selectInput("dataset", "Pick a dataset", ls("package:datasets")),
  tableOutput("preview"),
  downloadButton("download", "Download .tsv")
)
server <- function(input, output, session) {
  data <- reactive({
    out <- get(input$dataset, "package:datasets")
    if(!is.data.frame(out)){
      validate(paste0("'", input$dataset, "' is not a data frame"))}
    out
  })
  output$preview <- renderTable({head(data())})
  output$download <- downloadHandler(
    # returns a file name shown in the download dialog box
    filename = function(){paste0(input$dataset, ".tsv")},
    # argument file is the path to save the file. Saves the file
    # in a place Shiny knows about, so it can send it to the user
    content = function(file){vroom::vroom_write(data(), file)})
}
shinyApp(ui, server)

Appearance

Book chapter 6

Customize apps with the bslib package https://rstudio.github.io/bslib/

Bootswatch themes: https://bootswatch.com/

Preview themes with bslib::bs_theme_preview()

Customize app

In ui, pick a bootswatch theme to change the overall look of the Shiny app

theme = bslib::bs_theme(bootswatch = "darkly")

Customize plots

In the server() function, call

thematic::thematic_shiny()

to customize ggplot2, lattice and base plots to match the style of the app. This automatically determines all of the settings from the app theme

Appearance

library(ggplot2)

ui <- fluidPage(
  theme = bslib::bs_theme(bootswatch = "darkly"),
  
  sidebarLayout(
    sidebarPanel(
      textInput("txt", "Text input:", "text here"),
      sliderInput("slider", "Slider input:", 1, 100, 30)),
    mainPanel(
      plotOutput("plot"))
  )
)

server <- function(input, output, session){
  thematic::thematic_shiny()
  
  output$plot <- renderPlot({
    ggplot(mtcars, aes(wt, mpg)) + geom_point() + geom_smooth()
  })
}

shinyApp(ui, server)

Layout

https://shiny.posit.co/r/articles/build/layout-guide/

Laying out the components of an application:

  • Sidebar layout with sidebar panel for inputs and main panel for outputs
  • Grid layout where column widths should add up to 12
  • Tabsets for presenting the components using tabs
  • Navlists for presenting the components as a sidebar list
  • Navbar pages for multi-page user interface with navigation bar

shinyMobile package to develop mobile-ready Shiny applications

Exercise: Build a Shiny app with a grid layout that contains a row with two columns containing two plots, each column taking half the app

Exercise

library(shiny)

ui <- fluidPage(
  fluidRow(
    column(width = 6, plotOutput("plot1")),
    column(width = 6, plotOutput("plot2"))
  )
)
server <- function(input, output, session){
  output$plot1 <- renderPlot(plot(1:5))
  output$plot2 <- renderPlot(plot(1:5))
}

shinyApp(ui, server)

Shiny practical 2

Leaflet

https://rstudio.github.io/leaflet/shiny.html

library(leaflet)

ui <- fluidPage(
  leafletOutput("map"),
  actionButton("button", "New locations")
)

server <- function(input, output, session){
  
  # when button is pressed, generate 40 new locations
  points <- eventReactive(input$button, {cbind(rnorm(40)*2+13, rnorm(40)+48)}, ignoreNULL = FALSE)
  
  output$map <- renderLeaflet({
    leaflet() %>% addTiles() %>%
      addMarkers(data = points())
  })
}

shinyApp(ui, server)

Set ignoreNULL = FALSE to initially perform the calculation and let the user recalculate (reference)

Leaflet

This works, but reactive inputs and expressions that affect the renderLeaflet() expression will cause the entire map to be redrawn from scratch and reset the map position and zoom level

Use leafletProxy() instead of leaflet() to have more control over the map, such as changing the color of a single polygon or adding a marker at the point of a click without redrawing the entire map

Typically, use leaflet() to create the static aspects of the map, and leafletProxy() to modify the dynamic elements of a map that is already running in the page

Leaflet

library(leaflet)

ui <- fluidPage(
  leafletOutput("map"),
  actionButton("button", "New locations")
)

server <- function(input, output, session){
  # when button is pressed, generate 40 new locations
  points <- eventReactive(input$button,
  {cbind(rnorm(40)*2+13, rnorm(40)+48)}, ignoreNULL = FALSE)
  
  # leaflet() to include aspects that do not change in the map
  output$map <- renderLeaflet({
    leaflet() %>% addTiles() %>%
      setView(lng = 15, lat = 48, zoom = 6)
    })
  
  # leafletproxy() to include aspects that change in the map
  observe({
    leafletProxy("map") %>%
      clearMarkers() %>% addMarkers(data = points())
  })
}
shinyApp(ui, server)

Shiny practical 3

Interactive dashboards with
flexdashboard and Shiny

Interactive dashboards with
flexdashboard and Shiny

Flexdashboard

https://www.paulamoraga.com/book-geospatial/sec-flexdashboard

Flexdashboard and Shiny

https://www.paulamoraga.com/book-geospatial/sec-dashboardswithshiny

Add runtime: shiny to the YAML header

Add a column on the left-hand side of the dashboard to add the input controls. Add .{sidebar} to have a special background color

Add Shiny inputs and outputs as appropriate

Include visualizations by wrapping them in a call to render*({}) to dynamically respond to changes

Leaflet map with leafletProxy()

https://www.paulamoraga.com/book-geospatial/sec-dashboardswithshiny

# leaflet() to include aspects that do not change in the map
output$map <- renderLeaflet({
  leaflet() %>% addTiles() %>% setView(lng = 0, lat = 30, zoom = 2)
})

# leafletproxy() to include aspects that change in the map
observe({
  if (nrow(mapFiltered()) == 0) {return(NULL)}
  
  leafletProxy("map", session, data = mapFiltered()) %>%
    clearShapes() %>% clearControls() %>%
    addPolygons(fillColor = ~ pal(PM2.5), color = "white",
                fillOpacity = 0.7, label = ~labels,
                highlight = highlightOptions(color = "black",
                                         bringToFront = TRUE)) %>%
    leaflet::addLegend(pal = pal, values = ~PM2.5, opacity = 0.7, title = "PM2.5")
})

leafletOutput("map")

Interactive dashboards with shinyflexdashboard

Package shinydashboard provides another way to create dashboards with Shiny

https://rstudio.github.io/shinydashboard/

Building a Shiny app to upload and visualize spatio-temporal data

https://www.paulamoraga.com/book-geospatial/sec-shinyexample.html

Shiny practical 4

Sharing Shiny apps with shinyapps.io

Deploy a Shiny app:

https://docs.posit.co/shinyapps.io/guide/getting_started/index.html

Create a shinyapps.io account: https://www.shinyapps.io/

Configure rsconnect

library(rsconnect)
rsconnect::setAccountInfo(name = "<ACCOUNT>", token = "<TOKEN>",
                          secret = "<SECRET>")

Deploy Shiny app

library(rsconnect)
deployApp()

Before deployApp(), use setwd() to set the working directory to the folder where app.R is. Or use the argument appDir of deployApp() to set the folder where app.R is

Websites with quarto

Websites with quarto

Creating websites with quarto

https://quarto.org/docs/websites/

Publishing website to GitHub Pages

https://quarto.org/docs/publishing/github-pages.html

Quarto websites

File > New Project > New Directory > Quarto Website

Click Render

Publishing website to GitHub Pages

  • Render website to the docs directory by modifying the config file _quarto.yml

  • Create a repository in GitHub

  • Create project in RStudio with the repository URL

  • Copy contents previous folder to new project

  • Check the rendered website into GitHub

  • Configure GitHub Pages to publish from the docs directory

Render website to the docs directory

Add out-dir: docs to _quarto.yml to render website to the docs directory

Create GitHub repository for the website

In GitHub, create a repository with name mywebsite

Copy SSH git@github.com:Paula-Moraga/mywebsite.git

  • Create project in RStudio

In RStudio, File > New Project > Version Control > Git. Copy repository URL

Repository URL: git@github.com:Paula-Moraga/mywebsite.git

Project directory name: mywebsite

  • Copy contents previous folder of website to project mywebsite

  • Close RStudio and open mywebsite.Rproj

  • Render website

Check the rendered website into GitHub

In Terminal

git status
git add .
git commit -m "initial commit"
git push

GitHub Pages

Configure GitHub repo to publish from the docs directory

In GitHub, go to Settings, Pages, select master, select /docs, and click on Save

Your site is live at http://www.paulamoraga.com/mywebsite/






Join my research group at KAUST!

KAUST is an international university located on the shores of the Red Sea

All students receive a living allowance, free housing and medical coverage

👩‍💻 Potential research areas include the development of innovative statistical and computational methods for health and environmental applications

💪 Work closely with collaborators at KAUST and around the world

✈️ Generous travel funding for conferences and collaborative work

✨ Excellent research environment. Superb equipment and research facilities

    paulamoraga         www.PaulaMoraga.com         www.kaust.edu.sa     

References

Ribeiro Amaral, A., et al. (2022). Spatio-temporal modeling of infectious diseases by integrating compartment and point process models. SERRA, 37, 1519-1533

Moraga, P. and Baker, L. (2022). rspatialdata: a collection of data sources and tutorials on downloading and visualising spatial data using R. F1000Research, 11:77

Moraga, P., et al. (2019). epiflows: an R package for risk assessment of travel-related spread of disease. F1000Research, 7:1374

Moraga, P. (2018). Small Area Disease Risk Estimation and Visualization Using R. The R Journal, 10(1):495-506

Moraga, P. (2017). SpatialEpiApp: A Shiny Web Application for the analysis of Spatial and Spatio-Temporal Disease Data. Spatial and Spatio-temporal Epidemiology, 23:47-57

Moraga, P., et al. (2017). A geostatistical model for combined analysis of point-level and area-level data using INLA and SPDE. Spatial Statistics, 21:27-41

Moraga, P. and Kulldorff, M. (2016). Detection of spatial variations in temporal trends with a quadratic function. Statistical Methods for Medical Research, 25(4):1422-1437

Hagan, J. E., Moraga, P., et al. (2016). Spatio-temporal determinants of urban leptospirosis transmission: Four-year prospective cohort study of slum residents in Brazil. PLOS Neglected Tropical Diseases, 10(1): e0004275

Moraga, P., et al. (2015). Modelling the distribution and transmission intensity of lymphatic filariasis in sub-Saharan Africa prior to scaling up interventions: integrated use of geostatistical and mathematical modelling. Parasites & Vectors, 8:560



Thanks!

Paula Moraga


   paula.moraga@kaust.edu.sa
   www.PaulaMoraga.com

a png
       a png