R basics
This chapter provides some minimal set of R basics that may make it easier to read this book. A more comprehensive book on R basics is given in (Wickham 2014a), chapter 2.
Pipes
The %>%
(pipe) symbols should be read as then: we read
as with a
do b
then c
then d
with n
being 10, and that is just alternative syntax for
or
To many, the pipe-form is easier to read because execution order
follows reading order, from left to right. Like nested function
calls, it avoids the need to choose names for intermediate results,
but like nested function calls, it is hard to debug intermediate
results that diverge from out expectations. Note that the
intermediate results do exist in memory, so neither form saves
memory allocation. The pipe that appeared natively in R 4.1.0,
|>
, can be used anywhere in this book where %>%
is used. The
reason we kept to the %>%
pipe is to not exclude users of older
R version to use the code sections in this book.
Data structures
As pointed out by (Chambers 2016), everything that exists in R is an object. This includes objects that make things happen, such as language objects or functions, but also the more basic “things”, such as data objects. Some basic R data structures will now be discussed.
Homogeneous vectors
Data objects contain data, and possibly metadata. Data is always
in the form of a vector, which can have different type. We can
find the type by typeof
, and vector length by length
. Vectors
are created by c
, which combines individual elements:
# [1] "integer"
# [1] 10
# [1] "double"
# [1] 1
# [1] "character"
# [1] 2
# [1] "logical"
Vectors of this kind can only have a single type.
Note that vectors can have zero length, e.g. in,
# [1] "integer"
# integer(0)
# [1] 0
We can retrieve (or in assignments: replace) elements in a vector
using [
or [[
:
# [1] 2
# [1] 2
# [1] 2 3
# [1] 1 5 6
# [1] 1 5 10
where the difference is that [
can operate on an index range
(or multiple indexes), and [[
operates on a single vector value.
Heterogeneous vectors: list
An additional vector type is the list
, which can combine any types in
its elements:
# [1] "list"
# [1] 3
For lists, there is a further distinction between [
and [[
: the single
[
returns always a list, and [[
returns the contents of a list element:
# [[1]]
# [1] 3
# [1] 3
For replacement, one case use [
when providing a list, and [[
when providing
a new value:
# [[1]]
# [1] 4
#
# [[2]]
# [1] FALSE
#
# [[3]]
# [1] "foo"
# [[1]]
# [1] 4
#
# [[2]]
# [1] FALSE
#
# [[3]]
# [1] "bar"
In case list elements are named, as in
# $first
# [1] 3
#
# $second
# [1] TRUE
#
# $third
# [1] "foo"
we can use names as in l[["second"]]
and this can be
abbreviated to
# [1] TRUE
# $first
# [1] 3
#
# $second
# [1] FALSE
#
# $third
# [1] "foo"
This is convenient, but also requires name look-up in the names attribute (see below).
NULL and removing list elements
NULL
is the null value in R; it is special in the sense that it doesn’t work
in simple comparisons:
# logical(0)
# logical(0)
but has to be treated specially, using is.null
:
# [1] TRUE
When we want to remove one or more list elements, we can do so by creating a new list that does not contain the elements that needed removal, as in
# $first
# [1] 3
#
# $third
# [1] "foo"
but we can also assign NULL
to the element we want to eliminate:
# $first
# [1] 3
#
# $third
# [1] "foo"
Attributes
We can glue arbitrary metadata objects to data objects, as in
# [1] 1 2 3
# attr(,"some_meta_data")
# [1] "foo"
and this can be retrieved, or replaced by
# [1] "foo"
# [1] "bar"
In essence, the attribute of an object is a named list, and we can get or set the complete list by
# $some_meta_data
# [1] "bar"
# $some_meta_data
# [1] "foo"
A number of attributes are treated specially by R, see e.g. ?attributes
.
object class and class attribute
Every object in R “has a class”, meaning that class(obj)
returns
a character vector with the class of obj
. Some objects have
an implicit class, e.g. vectors
# [1] "integer"
# [1] "logical"
# [1] "character"
but we can also set the class explicit, either by using attr
or by
using class
in the left-hand side of an expression:
# [1] 1 2 3
# attr(,"class")
# [1] "foo"
# [1] "foo"
# $class
# [1] "foo"
in which case the newly set class overrides the earlier implicit class. This way,
we can add methods for class foo
, e.g. by
# [1] "an object of class foo with length 3"
Providing such methods are generally intended to create more usable software, but at the same time they may make the objects more opaque. It is sometimes useful to see what an object “is made of” by printing it after the class attribute is removed, as in
# [1] 1 2 3
As a more elaborate example, consider the case where a polygon is made using package sf:
# POLYGON ((0 0, 1 0, 1 1, 0 0))
which prints the well-known-text form; to understand what the data structure is like, we can use
# [[1]]
# [,1] [,2]
# [1,] 0 0
# [2,] 1 0
# [3,] 1 1
# [4,] 0 0
various names attributes
Named vectors carry their names in a names
attribute. We saw examples
for lists above, an example for a numeric vector is:
# second
# 4
# $names
# [1] "first" "second" "last"
More name attributes are e.g. dimnames
of matrices or arrays,
which not only names dimensions, but also the labels associated
with each of the dimensions:
# cols
# rows col1 col2
# row1 1 3
# row2 2 4
# $dim
# [1] 2 2
#
# $dimnames
# $dimnames$rows
# [1] "row1" "row2"
#
# $dimnames$cols
# [1] "col1" "col2"
Data.frame objects have rows and columns, and each have names:
# $names
# [1] "a" "b"
#
# $class
# [1] "data.frame"
#
# $row.names
# [1] 1 2 3
using structure
When programming, the pattern of adding or modifying attributes before returning an object is extremely common, an example being:
f = function(x) {
a = create_obj(x) # call some other function
attributes(a) = list(class = "foo", meta = 33)
a
}
The last two statements can be contracted in
f = function(x) {
a = create_obj(x) # call some other function
structure(a, class = "foo", meta = 33)
}
where function structure
adds, replaces, or (in case of value NULL
) removes
attributes from the object in its first argument.
dissecting a MULTIPOLYGON
We can use the above examples to dissect an sf
object with
MULTIPOLYGON
s into pieces. Suppose we use the nc
dataset,
we can see from the attributes of nc
,
# $names
# [1] "AREA" "PERIMETER" "CNTY_" "CNTY_ID" "NAME" "FIPS"
# [7] "FIPSNO" "CRESS_ID" "BIR74" "SID74" "NWBIR74" "BIR79"
# [13] "SID79" "NWBIR79" "geom"
#
# $row.names
# [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
# [19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
# [37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
# [55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
# [73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
# [91] 91 92 93 94 95 96 97 98 99 100
#
# $class
# [1] "sf" "tbl_df" "tbl" "data.frame"
#
# $sf_column
# [1] "geom"
#
# $agr
# AREA PERIMETER CNTY_ CNTY_ID NAME FIPS FIPSNO CRESS_ID
# <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
# BIR74 SID74 NWBIR74 BIR79 SID79 NWBIR79
# <NA> <NA> <NA> <NA> <NA> <NA>
# Levels: constant aggregate identity
that the geometry column is named geom
. When we take out this column,
# Geometry set for 100 features
# Geometry type: MULTIPOLYGON
# Dimension: XY
# Bounding box: xmin: -84.3 ymin: 33.9 xmax: -75.5 ymax: 36.6
# Geodetic CRS: NAD27
# First 5 geometries:
# MULTIPOLYGON (((-81.5 36.2, -81.5 36.3, -81.6 3...
# MULTIPOLYGON (((-81.2 36.4, -81.2 36.4, -81.3 3...
# MULTIPOLYGON (((-80.5 36.2, -80.5 36.3, -80.5 3...
# MULTIPOLYGON (((-76 36.3, -76 36.3, -76 36.3, -...
# MULTIPOLYGON (((-77.2 36.2, -77.2 36.2, -77.3 3...
we see an object that has the following attributes
# $n_empty
# [1] 0
#
# $crs
# Coordinate Reference System:
# User input: NAD27
# wkt:
# GEOGCRS["NAD27",
# DATUM["North American Datum 1927",
# ELLIPSOID["Clarke 1866",6378206.4,294.978698213898,
# LENGTHUNIT["metre",1]]],
# PRIMEM["Greenwich",0,
# ANGLEUNIT["degree",0.0174532925199433]],
# CS[ellipsoidal,2],
# AXIS["geodetic latitude (Lat)",north,
# ORDER[1],
# ANGLEUNIT["degree",0.0174532925199433]],
# AXIS["geodetic longitude (Lon)",east,
# ORDER[2],
# ANGLEUNIT["degree",0.0174532925199433]],
# USAGE[
# SCOPE["Geodesy."],
# AREA["North and central America: Antigua and Barbuda - onshore. Bahamas - onshore plus offshore over internal continental shelf only. Belize - onshore. British Virgin Islands - onshore. Canada onshore - Alberta, British Columbia, Manitoba, New Brunswick, Newfoundland and Labrador, Northwest Territories, Nova Scotia, Nunavut, Ontario, Prince Edward Island, Quebec, Saskatchewan and Yukon - plus offshore east coast. Cuba - onshore and offshore. El Salvador - onshore. Guatemala - onshore. Honduras - onshore. Panama - onshore. Puerto Rico - onshore. Mexico - onshore plus offshore east coast. Nicaragua - onshore. United States (USA) onshore and offshore - Alabama, Alaska, Arizona, Arkansas, California, Colorado, Connecticut, Delaware, Florida, Georgia, Idaho, Illinois, Indiana, Iowa, Kansas, Kentucky, Louisiana, Maine, Maryland, Massachusetts, Michigan, Minnesota, Mississippi, Missouri, Montana, Nebraska, Nevada, New Hampshire, New Jersey, New Mexico, New York, North Carolina, North Dakota, Ohio, Oklahoma, Oregon, Pennsylvania, Rhode Island, South Carolina, South Dakota, Tennessee, Texas, Utah, Vermont, Virginia, Washington, West Virginia, Wisconsin and Wyoming - plus offshore . US Virgin Islands - onshore."],
# BBOX[7.15,167.65,83.17,-47.74]],
# ID["EPSG",4267]]
#
# $class
# [1] "sfc_MULTIPOLYGON" "sfc"
#
# $precision
# [1] 0
#
# $bbox
# xmin ymin xmax ymax
# -84.3 33.9 -75.5 36.6
When we take the contents of the fourth list element, we obtain
# MULTIPOLYGON (((-76 36.3, -76 36.3, -76 36.3, -76 36.4, -76.1 36.3, -76.2 36.4, -76.2 36.4, -76.2 36.4, -76.3 36.6, -76.1 36.6, -76 36.6, -76 36.5, -76.1 36.5, -76 36.4, -76 36.4, -76 36.4, -76 36.4, -75.9 36.4, -75.9 36.4, -75.8 36.1, -75.8 36.1, -75.9 36.1, -75.9 36.2, -76 36.3, -75.9 36.3, -76 36.3)), ((-76 36.6, -76 36.6, -75.9 36.5, -75.9 36.5, -76 36.5, -76 36.5, -76 36.6)), ((-75.9 36.6, -75.9 36.6, -75.8 36.2, -75.8 36.2, -75.9 36.6)))
which is a list,
# [1] "list"
with attributes
# $class
# [1] "XY" "MULTIPOLYGON" "sfg"
and length
# [1] 3
The length indicates the number of outer rings: a multi-polygon can consist of more than one polygon. We see that most counties only have a single polygon:
# [1] 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
# [38] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
# [75] 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 2 1 1 1 1 1
A multi-polygon is a list with polygons,
# [1] "list"
and the first polygon of the fourth multi-polygon is again a list, because polygons have an outer ring possibly followed by multiple inner rings (holes)
# [1] "list"
we see that it contains only one ring, the exterior ring:
# [1] 1
and we can print type, the dimension and the first set of coordinates by
# [1] "double"
# [1] 26 2
# [,1] [,2]
# [1,] -76.0 36.3
# [2,] -76.0 36.3
# [3,] -76.0 36.3
# [4,] -76.0 36.4
# [5,] -76.1 36.3
# [6,] -76.2 36.4
and we can now for instance change the latitude of the third coordinate by
References
Chambers, John. 2016. Extending R. CRC Press.
Wickham, Hadley. 2014a. Advanced R, Second Edition. CRC Press. https://adv-r.hadley.nz/.