From ba9c102983ced372bde16b0b554997c99057dab5 Mon Sep 17 00:00:00 2001
From: SriramJallu
+[WUR Geoscripting](https://geoscripting-wur.github.io/)
# Intro to functions and refresher on R
From e8fab3d11e44ed0ecc12ed2f0321249b45e1f22c Mon Sep 17 00:00:00 2001
From: SriramJallu
## terra 1.8.60
+## terra 1.9.46
-## Linking to GEOS 3.12.1, GDAL 3.8.4, PROJ 9.4.0; sf_use_s2() is TRUE
+## Linking to GEOS 3.14.1, GDAL 3.12.2, PROJ 9.7.1; sf_use_s2() is TRUE
Similarly, we can create new directories for storing our data using
the functions dir.exists and dir.create.
Again, we check if these directories are missing before creating them,
@@ -1088,7 +1089,7 @@
list.files()# Check your working directory
+# Check your working directory; note that Positron automatically sets the working directory to the opened folder
getwd()
# List the files available in this directory
@@ -1111,7 +1112,7 @@ Example of glob2rx()
Example of paste() and paste0()
-## [1] "Today is Mon Aug 18 14:07:43 2025"
+## [1] "Today is Thu Aug 27 13:16:30 2026"
## [1] "A1" "A2" "A3" "A4" "A5" "A6"
@@ -1176,55 +1177,43 @@ Reading and writing data
read.csv() command. However, you can read in virtually any
type of text file. Type ?read.table in your console for
some other examples.
-
-## [1] "/home/osboxes/Documents/Scripting4GeoIntro"
-
-## [1] "1" "2" "3" "4" "5" "6, 7" "8, 9, 10"
-# Write to your working directory
-write.csv(test, file = "testing.csv")
-
-# Remove the variable "test" from the R working environment
-rm(test)
-
-# Check that the object is no longer in the working environment
-ls()
-## [1] "a" "b" "country" "countrycode" "d"
-## [6] "date" "date0" "e" "f" "name"
-
-## X x
-## 1 1 1
-## 2 2 2
-## 3 3 3
-## 4 4 4
-## 5 5 5
-## 6 6 6, 7
-## 7 7 8, 9, 10
+# Create some dummy data
+(test <- c(1:5, "6, 7", "8, 9, 10"))
+
+# Write to your working directory
+write.csv(test, file = "testing.csv")
+
+# Remove the variable "test" from the R working environment
+rm(test)
+
+# Check that the object is no longer in the working environment
+ls()
+
+# Read from your working directory
+(test <- read.csv("testing.csv"))
Writing a function
It is hard to unleash the full potential of R without writing your
own functions. Luckily it's very easy to do. Here are some trivial
examples:
-# Put the function arguments in () and the evaluation in {}
-add <- function(x){
- x + 1
-}
-
-add(4)
+# Put the function arguments in () and the evaluation in {}
+add <- function(x){
+ x + 1
+}
+
+add(4)
## [1] 5
An example of setting the default argument values for your
function:
-
+
## [1] 6
-
+
## [1] 7
That's about all there is to it. The function will generally return
the result of the last line that was evaluated.
@@ -1242,21 +1231,21 @@ Writing a function
specifies (in this case) two names. The value of the function appears
within the second set of brackets where the process applied to the named
objects from the argument list is defined.
-
+
Next, a new object a2b is created which contains the
result of applying newfunc to the two objects you have
defined earlier. The second R command below prints this new object to
the console.
-
+
## [1] 8 2 4
Finally, you can now remove the objects you have created to make room
for the next exercise by selecting and running the last line of the
code.
-
+
@@ -1289,8 +1278,10 @@ Good scripting/programming habits in R
you do so, you may find
your computer set on fire.
Set your working directory relative to where you saved your R
-script: go to Session in the RStudio menu bar → Set Working
-Directory → To Source File Location
+script: It varies for different IDEs. For example, Positron
+automatically sets the working directory to the opened folder, while for
+RStudio: go to Session in the RStudio menu bar → Set
+Working Directory → To Source File Location
@@ -1321,43 +1312,43 @@ Good scripting/programming habits in R
lot easier and you should try to take maximum advantage of them.
Below is an example of a function written with good practices and
without. First the good example:
-ageCalculator <- function(x) {
- # Function to calculate age from birth year
- # x (numeric) is the year you were born
- if(!is.numeric(x)) {
- stop("x must be of class numeric")
- } else { # x is numeric
- # Get today's date
- date <- Sys.Date()
- # extract year from date and subtract
- year <- as.numeric(format(date, "%Y"))
- if(year <= x) {
- stop("You aren't born yet")
- }
- age <- year - x
- }
- return(age)
-}
-
-ageCalculator(1985)
-## [1] 40
+ageCalculator <- function(x) {
+ # Function to calculate age from birth year
+ # x (numeric) is the year you were born
+ if(!is.numeric(x)) {
+ stop("x must be of class numeric")
+ } else { # x is numeric
+ # Get today's date
+ date <- Sys.Date()
+ # extract year from date and subtract
+ year <- as.numeric(format(date, "%Y"))
+ if(year <= x) {
+ stop("You aren't born yet")
+ }
+ age <- year - x
+ }
+ return(age)
+}
+
+ageCalculator(1985)
+## [1] 41
What a beautiful age for learning geoscripting!
Then the bad example:
-# DON'T DO THIS, BAD EXAMPLE!!!
-funTest_4 <- function(x) {
-if( !is.numeric(x))
-{
-stop("x must be of class numeric" )
- }
-else {
-a = Sys.Date()
-b<- as.numeric( format( a,"%Y"))
-b-x
-}
-}
-
-funTest_4(1985)
-## [1] 40
+# DON'T DO THIS, BAD EXAMPLE!!!
+funTest_4 <- function(x) {
+if( !is.numeric(x))
+{
+stop("x must be of class numeric" )
+ }
+else {
+a = Sys.Date()
+b<- as.numeric( format( a,"%Y"))
+b-x
+}
+}
+
+funTest_4(1985)
+## [1] 41
Note that this also does work. But which of the two is the easiest to
read, understand, and modify if needed? ... Exactly, the first one. So
let's look back at the examples and identify some differences:
@@ -1411,12 +1402,12 @@ Object classes and Control flow
environment belongs to a class. You can take advantage of that, using
control flow, to make your functions more flexible. First, let's
introduce a new class.
-# Check for the terra package and install if missing
-if(!"terra" %in% installed.packages()){install.packages("terra")}
-library(terra)
-
-c <- rast(ncol = 10, nrow = 10)
-class(c)
+# Check for the terra package and install if missing
+if(!"terra" %in% installed.packages()){install.packages("terra")}
+library(terra)
+
+c <- rast(ncol = 10, nrow = 10)
+class(c)
## [1] "SpatRaster"
## attr(,"package")
## [1] "terra"
@@ -1434,21 +1425,21 @@ Controlling the class of input variables of a function
the input variables. Using object class can greatly simplify this task.
For example let's imagine that you just wrote a simple Hello World
function.
-HelloWorld <- function (x) {
- hello <- sprintf('Hello %s', x)
- return(hello)
-}
-
-# Let's test it
-HelloWorld('john')
+HelloWorld <- function (x) {
+ hello <- sprintf('Hello %s', x)
+ return(hello)
+}
+
+# Let's test it
+HelloWorld('john')
## [1] "Hello john"
-
+
## [1] "Hello 2.5"
-
+
## [1] "Hello Devis" "Hello Martin"
-
+
## character(0)
-
+
## [1] "Hello 1:10"
## [2] "Hello c(\"Name\", \"Name\", \"Name\", \"Name\", \"Name\", \"Name\", \"Name\", \"Name\", \"Name\", \"Name\")"
Surprisingly enough, R is smart enough to give intuitive output in
@@ -1458,16 +1449,17 @@
Controlling the class of input variables of a function
in the last two cases, the output is not intuitive. We may want to only
allow passing character vectors to this function. We can do this with a
small change:
-HelloWorld <- function (x) {
- if (!is.character(x))
- stop('Object of class "character" expected for x')
-
- hello <- sprintf('Hello %s', x)
- return(hello)
-}
-
-HelloWorld(21)
-## Error in HelloWorld(21): Object of class "character" expected for x
+HelloWorld <- function (x) {
+ if (!is.character(x))
+ stop('Object of class "character" expected for x')
+
+ hello <- sprintf('Hello %s', x)
+ return(hello)
+}
+
+HelloWorld(21)
+## Error in `HelloWorld()`:
+## ! Object of class "character" expected for x
The function now throws an informative error when something not
supported is requested. These function argument "sanity checks" are
useful to avoid lengthy processing, when we know that the output of the
@@ -1486,37 +1478,38 @@
Controlling the class of input variables of a function
Note that most common object classes have their own logical function
(that returns TRUE or FALSE) to check what
class it is. For example:
-
+
## [1] TRUE
-
+
## [1] TRUE
-
+
## [1] FALSE
-
+
## [1] TRUE
You should always try to take maximum advantage of these small
utilities and check for classes and properties of your objects. This is
important in some cases that you might not think of in advance, for
instance, consider an object with more than one class:
-a = list(a = 1:10, b = "b")
-
-# We can also set a class (only do that if you make your own class!)
-class(a) = c("myclass", "list")
-
-## Error in if (class(a) == "list") {: the condition has length > 1
-
+a = list(a = 1:10, b = "b")
+
+# We can also set a class (only do that if you make your own class!)
+class(a) = c("myclass", "list")
+
+## Error in `if (class(a) == "list") ...`:
+## ! the condition has length > 1
+
## [1] "a is a list"
Also note that is.character(32) == TRUE is equivalent to
is.character(32). Therefore when checking logical
@@ -1529,35 +1522,34 @@
Controlling the class of input variables of a function
very slightly faster).
An example, with a function that subtracts 2 SpatRasters, with the
option to plot the resulting SpatRaster, or not.
-library(terra)
-
-# Function to subtract 2 SpatRasters
-minusRaster <- function(x, y, plot=FALSE) {
- z <- x - y
- if (plot) {
- plot(z, 1) # Plots the first layer of the resulting SpatRaster
- }
- return(z)
-}
-
-# Let's generate 2 SpatRasters. The first one is the R logo raster
-# converted to the terra package file format
-r <- rast(system.file("ex/logo.tif", package = "terra"))
-
-# The second SpatRaster is derived from the initial SpatRaster in order
-# to avoid issues of non matching extent or resolution, etc
-r2 <- r
-
-# Now we fill the second SpatRaster with new values
-# The /10 simply makes the result more spectacular
-r2[] <- (1:ncell(r2)) / 10
-
-# Simply performs the calculation
-r3 <- minusRaster(r, r2)
-
-# Now performs the calculation and plots the resulting SpatRaster
-r4 <- minusRaster(r, r2, plot=TRUE)
-
+library(terra)
+
+# Function to subtract 2 SpatRasters
+minusRaster <- function(x, y, plot=FALSE) {
+ z <- x - y
+ if (plot) {
+ plot(z, 1) # Plots the first layer of the resulting SpatRaster
+ }
+ return(z)
+}
+
+# Let's generate 2 SpatRasters. The first one is the R logo raster
+# converted to the terra package file format
+r <- rast(system.file("ex/logo.tif", package = "terra"))
+
+# The second SpatRaster is derived from the initial SpatRaster in order
+# to avoid issues of non matching extent or resolution, etc
+r2 <- r
+
+# Now we fill the second SpatRaster with new values
+# The /10 simply makes the result more spectacular
+r2[] <- (1:ncell(r2)) / 10
+
+# Simply performs the calculation
+r3 <- minusRaster(r, r2)
+
+# Now performs the calculation and plots the resulting SpatRaster
+r4 <- minusRaster(r, r2, plot=TRUE)
@@ -1566,20 +1558,20 @@ Vectorised functions
capable of taking vectors, rather than individual values, as input. This
allows very simple and powerful syntax without needing to use loops, for
instance:
-NumVec = 1:10
-
-# We do not need to run the function on each element individually
-as.character(NumVec)
+NumVec = 1:10
+
+# We do not need to run the function on each element individually
+as.character(NumVec)
## [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
-
+
## [1] 5.5
-
+
## [1] 1 10
-
+
## [1] 101 102 103 104 105 106 107 108 109 110
-
+
## [1] 1 4 9 16 25 36 49 64 81 100
Because most base functions are already vectorised, it is easy to
write new functions that are themselves vectorised. For the most part,
@@ -1587,15 +1579,15 @@
Vectorised functions
also vectors of values, and think whether the result makes sense.
Vectorisation also allows us to write short and versatile code. For
instance, to check whether the input is a positive number:
-
+
## [1] TRUE
-
+
## [1] FALSE TRUE NA
-
+
## [1] FALSE FALSE
@@ -1605,7 +1597,7 @@ Vectorised functions
-
+
## [1] TRUE FALSE TRUE
@@ -1616,19 +1608,19 @@ Type consistency
logical value (TRUE, FALSE, or
also NA). This is called type consistency and is a useful
property. Consider code like this:
-AddNewSubject = function(NewBirthYear) {
- # Birth years of previous subjects
- BirthYears = c(1980, 1985, 1987, 1990, 1993, 1994, 1998, 2000)
- return(c(BirthYears, NewBirthYear))
-}
-
-# Works as expected
-NewSubjects = AddNewSubject(c(1995, 1998, 1999, 2000))
-sum(NewSubjects >= 2000) # How many subjects are born on or after 2000
+AddNewSubject = function(NewBirthYear) {
+ # Birth years of previous subjects
+ BirthYears = c(1980, 1985, 1987, 1990, 1993, 1994, 1998, 2000)
+ return(c(BirthYears, NewBirthYear))
+}
+
+# Works as expected
+NewSubjects = AddNewSubject(c(1995, 1998, 1999, 2000))
+sum(NewSubjects >= 2000) # How many subjects are born on or after 2000
## [1] 2
-# Whoops!
-NewSubjects = AddNewSubject(c("MCMXCV", "1998", "1999", "2000"))
-sum(NewSubjects >= 2000)
+# Whoops!
+NewSubjects = AddNewSubject(c("MCMXCV", "1998", "1999", "2000"))
+sum(NewSubjects >= 2000)
## [1] 3
@@ -1674,73 +1666,74 @@ Use of try() for error handling
processing chain to make your function more adaptive. See the example
below that illustrate the use of try() for sequentially
calculating frequency on a list of auto-generated SpatRasters.
-library(terra)
-
-# Create a SpatRaster and fill it with "randomly" generated integer values
-a <- rast(nrow = 50, ncol = 50)
-a[] <- floor(rnorm(n = ncell(a)))
-
-# The freq() function returns the frequency of a certain value in a SpatRaster
-# We want to know how many times the value -2 is present in the SpatRaster
-freq(a, value = -2)$count
-## [1] 311
-# Let's imagine that you want to run this function over a whole list of SpatRaster
-# but some elements of the list are impredictibly corrupted, so the list looks as follows
-b <- a
-c <- NA
-rasterList <- list(a, b, c)
-
-# Now, b and a are SpatRasters, and c is ''corrupted''
-# Running freq(c) would return an error and stop the whole process
-out <- list()
-for(i in 1:length(rasterList)) {
- out[i] <- freq(rasterList[[i]], value = -2)$count
-}
-## Error: unable to find an inherited method for function 'freq' for signature 'x = "logical"'
-# If you wrap the call in a try(), you still get an error, but it's non-fatal
-out <- list()
-for(i in 1:length(rasterList)) {
- out[i] <- try(freq(rasterList[[i]], value = -2)$count)
-}
+library(terra)
+
+# Create a SpatRaster and fill it with "randomly" generated integer values
+a <- rast(nrow = 50, ncol = 50)
+a[] <- floor(rnorm(n = ncell(a)))
+
+# The freq() function returns the frequency of a certain value in a SpatRaster
+# We want to know how many times the value -2 is present in the SpatRaster
+freq(a, value = -2)$count
+## [1] 368
+# Let's imagine that you want to run this function over a whole list of SpatRaster
+# but some elements of the list are impredictibly corrupted, so the list looks as follows
+b <- a
+c <- NA
+rasterList <- list(a, b, c)
+
+# Now, b and a are SpatRasters, and c is ''corrupted''
+# Running freq(c) would return an error and stop the whole process
+out <- list()
+for(i in 1:length(rasterList)) {
+ out[i] <- freq(rasterList[[i]], value = -2)$count
+}
+## Error:
+## ! unable to find an inherited method for function 'freq' for signature 'x = "logical"'
+# If you wrap the call in a try(), you still get an error, but it's non-fatal
+out <- list()
+for(i in 1:length(rasterList)) {
+ out[i] <- try(freq(rasterList[[i]], value = -2)$count)
+}
## Error : unable to find an inherited method for function 'freq' for signature 'x = "logical"'
-
+
## [[1]]
-## [1] 311
+## [1] 368
##
## [[2]]
-## [1] 311
+## [1] 368
##
## [[3]]
## [1] "Error : unable to find an inherited method for function 'freq' for signature 'x = \"logical\"'\n"
-# By building a function that includes a try() we are able to catch the error
-# without having it printed, allowing the process to handle the error gracefully.
-fun <- function(x, value) {
- tr <- try(freq(x = x, value = value)$count, silent=TRUE)
- if (class(tr) == 'try-error') {
- return('This object returned an error')
- } else {
- return(tr)
- }
-}
-
-# Let's try to run the loop again
-out <- list()
-for(i in 1:length(rasterList)) {
- out[i] <- fun(rasterList[[i]], value = -2)
-}
-out
+# By building a function that includes a try() we are able to catch the error
+# without having it printed, allowing the process to handle the error gracefully.
+fun <- function(x, value) {
+ tr <- try(freq(x = x, value = value)$count, silent=TRUE)
+ if (class(tr) == 'try-error') {
+ return('This object returned an error')
+ } else {
+ return(tr)
+ }
+}
+
+# Let's try to run the loop again
+out <- list()
+for(i in 1:length(rasterList)) {
+ out[i] <- fun(rasterList[[i]], value = -2)
+}
+out
## [[1]]
-## [1] 311
+## [1] 368
##
## [[2]]
-## [1] 311
+## [1] 368
##
## [[3]]
## [1] "This object returned an error"
-# Note that using a function of the apply family would be a more
-# elegant/shorter way to obtain the same result
-(out <- sapply(X = rasterList, FUN = fun, value = -2))
-## [1] "311" "311"
+# Note that using a function of the apply family would be a more
+# elegant/shorter way to obtain the same result
+(out <- sapply(X = rasterList, FUN = fun, value = -2))
+## [1] "368" "368"
## [3] "This object returned an error"
@@ -1763,28 +1756,28 @@ traceback() and debugonce()
Carefully reading the return of that function will tell you where
exactly in your function the error occurred.
-foo <- function(x) {
- x <- x + 2
- print(x)
- bar(2)
-}
-
-bar <- function(x) {
- x <- x + a.variable.which.does.not.exist
- print(x)
-}
-
-foo(2)
-# gives an error
-
-traceback()
-## 2: bar(2) at #1
-## 1: foo(2)
-# Ah, bar() is the problem
-
-# Debug it by declaring what to debug and running it
-debugonce(bar)
-foo(2)
+foo <- function(x) {
+ x <- x + 2
+ print(x)
+ bar(2)
+}
+
+bar <- function(x) {
+ x <- x + a.variable.which.does.not.exist
+ print(x)
+}
+
+foo(2)
+# gives an error
+
+traceback()
+## 2: bar(2) at #1
+## 1: foo(2)
+# Ah, bar() is the problem
+
+# Debug it by declaring what to debug and running it
+debugonce(bar)
+foo(2)
Depending on the IDE you are using, you may be presented with tools
for stepping through the function line by line, as well as a Browse
console, which allows you to query the state of the variables involved
@@ -1794,33 +1787,32 @@
traceback() and debugonce()
-
-RStudio
-RStudio has integration with the debugging tools in R, so you can use
-a point-and-click interface. However, some parts of it are specific to
-the RStudio IDE.
+
+Positron
+Positron has integration with the debugging tools in R, so you can
+use a point-and-click interface. However, some parts of it are specific
+to the Positron IDE.
-- To force them to catch every error, select Debug - On Error -
-Break in Code in the main menu.
+- To force them to catch every error, cehck the box Breakpoints -
+Errors in the main menu, bottom left.
- Run again
foo(2).
-- RStudio will stop the execution where the error happened. The
-traceback appears in a separate pane on the right.
-- You can and use the little green "Next" button to go line by line
-through the code, or the red Stop button to leave the debugging
-mode.
-- Reset the On Error behaviour to Error Inspector.
-In this default setting, RStudio will try to decide whether the error is
-complex enough for debugging, and then offer the options to "traceback"
-or "rerun the code with debugging" with two buttons in the console.
+- Positron will stop the execution where the error happened. The
+traceback will be highlighted in the code.
+- You can and use the little blue "Continue (F5)" button to go line by
+line through the code, or the red "Disconnect (Shift + F5)" button to
+leave the debugging mode.
+- Reset the Errors behaviour by unchecking the box. In this
+default setting, Positron offers the options to "traceback" with a
+button in the console.
Finally, solve the problem:
-
+
Refer to the reference section of this document for further
information on function debugging.
@@ -1832,50 +1824,48 @@ Creating a map within R - a simple demo
Here is an example of how you can create a map in R. We will
make use of a subset of the Global
Adminstrative Areas database (GADM):
-# Create data and output directories and download data from URL
-data_URL <- "https://github.com/GeoScripting-WUR/Scripting4GeoIntro/releases/download/gadm-data/gadm41_PHL_2.zip"
-data_dir <- "data"
-
-if (!dir.exists(data_dir)) {
- dir.create(data_dir)
-}
-
-if (!file.exists('data/data.zip')) {
- download.file(url = data_URL, destfile = file.path(data_dir, "data.zip"))
- unzip('data/data.zip', exdir = 'data')
-}
+# Create data and output directories and download data from URL
+data_URL <- "https://github.com/GeoScripting-WUR/Scripting4GeoIntro/releases/download/gadm-data/gadm41_PHL_2.zip"
+data_dir <- "data"
+
+if (!dir.exists(data_dir)) {
+ dir.create(data_dir)
+}
+
+if (!file.exists('data/data.zip')) {
+ download.file(url = data_URL, destfile = file.path(data_dir, "data.zip"))
+ unzip('data/data.zip', exdir = 'data')
+}
We load the administrative boundaries of the Philippines, using the
sf package, to which you will be introduced more in-depth
in a later tutorial:
-# Check for the sf package and install if missing
-if(!"sf" %in% installed.packages()){install.packages("sf")}
-library(sf)
-
-# Load the data and subset its geometry
-adm <- st_read(file.path(data_dir, "gadm41_PHL_2.json"), quiet = TRUE)
-adm_geom <- st_geometry(adm)
-
-# Create and example plot
-plot(adm_geom[adm$NAME_1 == "Tarlac"])
-
+# Check for the sf package and install if missing
+if(!"sf" %in% installed.packages()){install.packages("sf")}
+library(sf)
+
+# Load the data and subset its geometry
+adm <- st_read(file.path(data_dir, "gadm41_PHL_2.json"), quiet = TRUE)
+adm_geom <- st_geometry(adm)
+
+# Create and example plot
+plot(adm_geom[adm$NAME_1 == "Tarlac"])
Try to understand the code below, and let us know if you have
questions. Feel free to use this code as an example for exercise 5.
-mar <- adm_geom[adm$NAME_1 == "Marinduque"]
-plot(mar, bg = "dodgerblue", axes = TRUE)
-plot(mar, lwd = 10, border = "skyblue", add = TRUE)
-plot(mar, col = "green4", add = TRUE)
-grid()
-box()
-invisible(text(st_coordinates(st_centroid(mar)),
- labels = as.character(adm$NAME_2[adm$NAME_1 == "Marinduque"]), cex = 1.1, col = "white", font = 2))
-mtext(side = 3, line = 1, "Provincial Map of Marinduque", cex = 2)
-mtext(side = 1, "Longitude", line = 2.5, cex = 1.1)
-mtext(side = 2, "Latitude", line = 2.5, cex = 1.1)
-mtext(side = 1, line = -2,
-"Projection: Geographic\n
-Coordinate System: WGS 1984 \n
-Data Source: GADM.org ", adj = 1, cex = 0.5, col = "grey20")
-
+mar <- adm_geom[adm$NAME_1 == "Marinduque"]
+plot(mar, bg = "dodgerblue", axes = TRUE)
+plot(mar, lwd = 10, border = "skyblue", add = TRUE)
+plot(mar, col = "green4", add = TRUE)
+grid()
+box()
+invisible(text(st_coordinates(st_centroid(mar)),
+ labels = as.character(adm$NAME_2[adm$NAME_1 == "Marinduque"]), cex = 1.1, col = "white", font = 2))
+mtext(side = 3, line = 1, "Provincial Map of Marinduque", cex = 2)
+mtext(side = 1, "Longitude", line = 2.5, cex = 1.1)
+mtext(side = 2, "Latitude", line = 2.5, cex = 1.1)
+mtext(side = 1, line = -2,
+"Projection: Geographic\n
+Coordinate System: WGS 1984 \n
+Data Source: GADM.org ", adj = 1, cex = 0.5, col = "grey20")
References and more info