Understanding sp Objects

Mazama Science

2020-07-06

Understanding ‘sp’ Objectss

To work efficiently with the SpatialPolygonsDataFrame (SPDF) objects used in the MazamaSpatialUtils package, it is important to understand the S4 object classes that make up the sp package. This vignette introduces simple concepts associated with R’s object classes so that newcomers to this style of coding can work efficiently with SPDFs.

Objects in R

Unlike C, Java or python, R is a proudly “functional language”. The basic element is a function rather than an object. Over the years, various object systems have been developed for R and these are concisely explained in the Object Oriented Programming chapter from Wickham’s Advanced R.

We will discuss on S3 and S4 objects with a focus on how to tell which is which and how to manipulate them.

S3 Objects

When programming in R (as opposed to scripting), it is important to understand the fundamental nature of the “things” you are working with. The most common type of “thing” is an object of type S3. We will start by investigating a familiar function and its return value and learn the following:

What is an S3 Object

# Plot a histogram but save the return value
h <- hist(rnorm(100), plot = FALSE)

# Query this 'object'
typeof(h)
## [1] "list"
class(h)
## [1] "histogram"
attributes(h)
## $names
## [1] "breaks"   "counts"   "density"  "mids"     "xname"    "equidist"
## 
## $class
## [1] "histogram"
names(h)
## [1] "breaks"   "counts"   "density"  "mids"     "xname"    "equidist"

OK. So h has an internal storage type (typeof()) of list; and a class attribute of histogram. A simpler way to state this would be to say that “h is a list of class histogram.”

Accessing S3 components

You can access elements of this object in normal “list” fashion:

# What types of elements do we have?

str(lapply(h, class))
## List of 6
##  $ breaks  : chr "numeric"
##  $ counts  : chr "integer"
##  $ density : chr "numeric"
##  $ mids    : chr "numeric"
##  $ xname   : chr "character"
##  $ equidist: chr "logical"
# Let's look a "counts"
h$counts
##  [1]  1  3  4 21 16 25 12 11  5  1  1
# Or, alternatively
h[["counts"]]
##  [1]  1  3  4 21 16 25 12 11  5  1  1

Calling S3 “methods”

Let’s examine how R creates different plots for different types of “things”.

# Lets set up some more "objects"
h1 <- 1:10        # Numbers 1-10

layout(matrix(seq(2)))
plot(h)
plot(h1)

layout(1)

This capability demonstrates something called “method dispatch” where a top level function assess the class of an S3 object and calls the appropriate sub-function.

class(h)
## [1] "histogram"
class(h1)
## [1] "integer"
methods("plot")
##  [1] plot,ANY,ANY-method                       
##  [2] plot,Spatial,missing-method               
##  [3] plot,SpatialCollections,missing-method    
##  [4] plot,SpatialGrid,missing-method           
##  [5] plot,SpatialGridDataFrame,missing-method  
##  [6] plot,SpatialLines,missing-method          
##  [7] plot,SpatialMultiPoints,missing-method    
##  [8] plot,SpatialPixels,missing-method         
##  [9] plot,SpatialPixelsDataFrame,missing-method
## [10] plot,SpatialPoints,missing-method         
## [11] plot,SpatialPolygons,missing-method       
## [12] plot,SpatialRings,missing-method          
## [13] plot,gpc.poly,ANY-method                  
## [14] plot.HoltWinters*                         
## [15] plot.TukeyHSD*                            
## [16] plot.acf*                                 
## [17] plot.data.frame*                          
## [18] plot.decomposed.ts*                       
## [19] plot.default                              
## [20] plot.dendrogram*                          
## [21] plot.density*                             
## [22] plot.ecdf                                 
## [23] plot.factor*                              
## [24] plot.formula*                             
## [25] plot.function                             
## [26] plot.hclust*                              
## [27] plot.histogram*                           
## [28] plot.isoreg*                              
## [29] plot.lm*                                  
## [30] plot.medpolish*                           
## [31] plot.mlm*                                 
## [32] plot.ppr*                                 
## [33] plot.prcomp*                              
## [34] plot.princomp*                            
## [35] plot.profile.nls*                         
## [36] plot.raster*                              
## [37] plot.shingle*                             
## [38] plot.spec*                                
## [39] plot.stepfun                              
## [40] plot.stl*                                 
## [41] plot.table*                               
## [42] plot.trellis*                             
## [43] plot.ts                                   
## [44] plot.tskernel*                            
## see '?methods' for accessing help and source code

It turns out there are lots of “plot” functions. If you try to plot an object of class histogram you will end up calling plot.histogram whereas plotting an object of class integer ends up calling plot.default.

S4 Objects

While S3 objects are simple, they do no allow many of the fancier features associated with “Object Oriented Programming” (OOP). As R evolved, people came up with S4 objects to address this. We won’t go into the details about what is possible but just point out how to use S4 objects from the sp package.

What is an S4 Object

The MazamaSpatialUtils package uses sp and creates S4 objects:

# Load the 'SimpleCountries' dataset
library(MazamaSpatialUtils)
data("SimpleCountries")

class(SimpleCountries)
## [1] "SpatialPolygonsDataFrame"
## attr(,"package")
## [1] "sp"
typeof(SimpleCountries)
## [1] "S4"
# It's big so we don't just print out the attributes
a <- attributes(SimpleCountries)
class(a)
## [1] "list"
names(a)
## [1] "data"        "polygons"    "plotOrder"   "bbox"        "proj4string"
## [6] "class"

Accessing S4 components

# S4 objects have 'slots'
slotNames(SimpleCountries)
## [1] "data"        "polygons"    "plotOrder"   "bbox"        "proj4string"
# Access slots using 'slot()' or '@'
class(slot(SimpleCountries, "data"))
## [1] "data.frame"
class(SimpleCountries@data)
## [1] "data.frame"

SPDF Objects

Each SpatialPolygonsDataFrame is an S4 object with a 1:1 relationship between records (rows) in the @data dataframe and Polygons S4 objects in the @polygons list. This makes it possible to use some functions like subset() that work on dataframes. The following code shows how to work with them.

# Compare 'data' and 'polygons' slots
nrow(SimpleCountries@data)
## [1] 246
length(SimpleCountries@polygons)
## [1] 246
# Look at the first record
str(SimpleCountries@data[1,])
## 'data.frame':    1 obs. of  6 variables:
##  $ countryCode: chr "AG"
##  $ countryName: chr "Antigua and Barbuda"
##  $ ISO3       : chr "ATG"
##  $ FIPS       : chr "AC"
##  $ UN_region  : int 19
##  $ polygonID  : chr "AG"
# We can also use normal dataframe syntax to access columns of data
SimpleCountries$countryName[1]
## [1] "Antigua and Barbuda"
# Magic happens when we plot using normal dataframe syntax
plot(SimpleCountries[1,])

# Here is a more advanced example using pipes
SimpleCountries %>% 
  subset(UN_region == 150) %>% 
  subset(countryCode != "RU") %>% 
  plot()

SimpleCountries %>%
  subset(countryCode %in% c("DE", "AT", "CH")) %>%
  plot(col = 'red', add = TRUE)

title("German Speakers in Europe")


Best of luck making interesting maps!

Mazama Science