# 3 R Basics

## 3.1 Assignment Operator

You assign an R object to a value using the assignment operator `<-`

or the equal sign. `<-`

is read as “gets”.

```
<- y
x <- read.csv('mydata.csv')
d = y x
```

## 3.2 Object Types

Everything in R is an object. This includes functions, as demonstrated in the example below.

```
<- sqrt # copy the square root function as f
f f(16) # equivalent to sqrt(16)
```

`[1] 4`

### 3.2.1 Object Names

The names of objects are always case-sensitive in R. If the age variable is named `Age`

, typing `age`

may result in an “object not found” error. Valid symbols in object names are upper and lower case letters, numbers, periods, and underscores. A name may not begin with a number. There are contexts in which you may include any characters in object names, including spaces:

- for extracting columns in data frames, data tables, matrices, and lists you can single- or double-quote the name, e.g.
`mydata[, "age in years"]`

- for wider contexts (e.g. statistical model formulas) you can put single back-ticks around the name

### 3.2.2 Objects

Some primitive types of objects in R are below.

Type | Meaning |
---|---|

integer | whole numbers |

logical | values of `TRUE` or `FALSE` |

double | floating point non-whole numbers |

character | character strings |

function | code defining a function |

In the table below, objects of different shapes are described. `rows`

and `cols`

refers to vectors of integers or logicals, or if the elements of the object are named, character strings.

Type | Example | Values Retrieved By |
---|---|---|

scalar | `x <- 3` |
`x` |

vector | `y <- c(1, 2, 5)` |
`y[2]` (2), `y[2:3]` (2, 5), `y[-1]` (2, 5), `y[c(TRUE,FALSE,TRUE)]` (1, 5) |

named vector | `y <- c(a=1, b=2, d=5)` |
`y[2]` (2), `y['b']` (2), `y[c('a','b')]` (1, 2) |

matrix | `y <- cbind(1:3, 4:5)` |
`y[rows,cols]` , `y[rows,]` (all cols), `y[,cols]` (all rows) |

array | `y <- array(1:30, dim=c(2,3,5))` |
`y[1,1,1]` (1), `y[2,3,5]` (30) |

list | `x <- list(a='cat', b=c(1,3,7))` |
`x$a` (‘cat’), `x[[1]]` (‘cat’), `x[['a']]` (‘cat’) |

Named vectors provide an extremely quick table lookup and recoding capability.

`list`

objects are arbitrary trees and can have elements nested to any level. You can have lists of lists or lists of data frames/tables.

Vectors can be of many different types when a `class`

is added to them. Two of the most common are `Date`

s and `factor`

s. Character strings are handled very efficiently in R so there is not always a need to store categorical variables as `factor`

s. But there is one reason: to order levels, i.e., distinct variable values, so that tabular and graphical output will list values in a more logical order than alphabetic. A factor variable has a `levels`

*attribute* added to it to accomplish this. An example is `x <- factor(x, 1:3, c('cat', 'dog', 'fox'))`

where the second argument `1:3`

is the vector of possible numeric values `x`

currently takes on (in order) and the three character strings are the corresponding `levels`

. Internally `factors`

are coded as integers, but they print as character strings.

Rectangular data objects, i.e., when the number of rows is the same for every column (variable), can be represented by matrices, arrays, `data.frame`

s, and `data.table`

s. In a matrix or array, every value is of the same type. A `data.frame`

or a `data.table`

is an R `list`

that can have mixtures of numeric, character, factor, dates, and other object types. A `data.table`

is also a `data.frame`

but the converse isn’t true. `data.table`

s are handled by the R `data.table`

package and don’t have row names but can be indexed, are much faster to process, and have a host of methods implemented for aggregation and other operations. `data.frame`

s are handled by base R.

Data frames are best managed by converting them to data tables and using the `data.table`

package. When `data.table`

is not used there are three indispensable functions for operating on data frames:

`with`

for analyzing variables within a data frame without constantly prefixing variable names with`dataframename$`

`transform`

for adding or changing variables within a data frame`Hmisc`

`upData`

function for doing the same as`transform`

but also allowing metadata to be added to the data, e.g., variable labels and units (to be discussed later)

Here are some examples of `with`

and `transform`

.

```
# Better than mean(mydata$systolic.bp - mydata$diastolic.bp) :
with(mydata, mean(systolic.bp - diastolic.bp))
# Better than mydata$pulse.pressure <- mydata$systolic.bp - mydata$diastolic.bp:
<- transform(mydata,
mydata pulse.pressure = systolic.bp - diastolic.bp,
bmi = wt / ht ^ 2)
# Perform several operations on the same data frame
with(mydata, {
<- x1 / sqrt(x2)
x3 ols(y ~ x3)
} )
```

## 3.3 Dates and Time

R and add-on packages provide many ways to process dates and date-time variables. Let’s look at methods provided by base R. The most standard date format is ISO 8601 with its YYYY-MM-DD format, and character variables in this format are easily converted to numeric date form using the `as.Date`

function on which arithmetic and statistical analysis can be performed. The date is stored internally as the number of days from 1970-01-01. `as.Date`

will also convert from other formats. Here are some examples, which include computing days between dates. Unlike these examples the most common date conversions occur on imported character vectors. Some other functions exemplified below are

`difftime`

: compute differences between dates in days or months`weekdays`

: retrieve the name of the day of the week for given dates`seq`

: regularly sequence dates`format`

: reformat dates and extract year, month, or day`Sys.date`

,`Sys.time`

: current date or date/time

`as.Date('1970-01-01')`

`[1] "1970-01-01"`

`unclass(as.Date('1970-01-01')) # 0 since 1970-01-01 is the default origin`

`[1] 0`

`as.Date('01/02/2023', format='%m/%d/%Y')`

`[1] "2023-01-02"`

`as.Date('1/2/2023', format='%m/%d/%Y')`

`[1] "2023-01-02"`

`as.Date('1/2/23', format='%m/%d/%y') # not safe for wide date ranges`

`[1] "2023-01-02"`

`as.Date('2jan1960', format='%d%b%Y')`

`[1] "1960-01-02"`

```
<- as.Date(c('2023-02-01', '2023-03-01'))
x x
```

`[1] "2023-02-01" "2023-03-01"`

`weekdays(x)`

`[1] "Wednesday" "Wednesday"`

`seq(x[1], x[2], by='week')`

`[1] "2023-02-01" "2023-02-08" "2023-02-15" "2023-02-22" "2023-03-01"`

`seq(x[1], x[2], by='2 weeks')`

`[1] "2023-02-01" "2023-02-15" "2023-03-01"`

`seq(x[1], x[2], by='month')`

`[1] "2023-02-01" "2023-03-01"`

`seq(x[1], x[2], by='year')`

`[1] "2023-02-01"`

`2] - x[1] x[`

`Time difference of 28 days`

`+ 35 x `

`[1] "2023-03-08" "2023-04-05"`

`class(x[2] - x[1])`

`[1] "difftime"`

`as.numeric(x[2] - x[1]) # convert to regular numeric`

`[1] 28`

`as.numeric(x[2] - x[1]) / 365.25 # years + fraction`

`[1] 0.07665982`

```
# The following is particularly good for statistical analysis of absolute dates
as.numeric(x - as.Date('2020-01-01')) / 365.25 # years + fraction since 2020-01-01
```

`[1] 3.085558 3.162218`

`difftime(x[2], x[1])`

`Time difference of 28 days`

`difftime(x, as.Date('2000-01-01'))`

```
Time differences in days
[1] 8432 8460
```

`difftime(x, as.Date('2000-01-01'), units='weeks')`

```
Time differences in weeks
[1] 1204.571 1208.571
```

`format(x, '%Y')`

`[1] "2023" "2023"`

`format(x, '%m')`

`[1] "02" "03"`

`format(x, '%d')`

`[1] "01" "01"`

`as.numeric(format(x, '%m'))`

`[1] 2 3`

`format(x, '%A')`

`[1] "Wednesday" "Wednesday"`

`format(x, '%a')`

`[1] "Wed" "Wed"`

`Sys.Date() # today`

`[1] "2023-11-17"`

`Sys.time() # current date and time`

`[1] "2023-11-17 08:47:30 CST"`

For storage of date-time variables it’s best to use the `POSIXct`

class. Internally values are stored as seconds from midnight 1970-01-01. Here are some examples.

`strptime`

and `as.POSIXct`

.```
<- '%Y-%m-%d %H:%M:%S'
f as.POSIXct('1970-01-01 00:00:01', format=f) # uses current time zone
```

`[1] "1970-01-01 00:00:01 CST"`

`as.POSIXct('1970-01-01 00:00:01', format=f, tz='GMT') # universal time zone`

`[1] "1970-01-01 00:00:01 GMT"`

`unclass(as.POSIXct('1970-01-01 00:00:01', format=f, tz='GMT')) # 1s past midnight 1970-01-01`

```
[1] 1
attr(,"tzone")
[1] "GMT"
```

```
<- as.POSIXct(c('2023-02-01 11:03:00', '2023-03-01 10:04:00'), format=f)
x x
```

`[1] "2023-02-01 11:03:00 CST" "2023-03-01 10:04:00 CST"`

`diff(x)`

`Time difference of 27.95903 days`

`2] - x[1] x[`

`Time difference of 27.95903 days`

`difftime(x[2], x[1], units='days')`

`Time difference of 27.95903 days`

`as.numeric(difftime(x[2], x[1], units='days'))`

`[1] 27.95903`

`difftime(x[2], x[1], units='weeks')`

`Time difference of 3.994147 weeks`

`difftime(x[2], x[1], units='hours')`

`Time difference of 671.0167 hours`

`difftime(x[2], x[1], units='mins')`

`Time difference of 40261 mins`

`1] + 1 # add one second x[`

`[1] "2023-02-01 11:03:01 CST"`

`1] + 60 # add one minute x[`

`[1] "2023-02-01 11:04:00 CST"`

`1] + 60*60 # add one hour x[`

`[1] "2023-02-01 12:03:00 CST"`

`1] + 24*60*60 # add one day x[`

`[1] "2023-02-02 11:03:00 CST"`

`format(x, '%Y')`

`[1] "2023" "2023"`

`as.numeric(format(x, '%m'))`

`[1] 2 3`

`format(x, '%A')`

`[1] "Wednesday" "Wednesday"`

`format(x, '%H:%M')`

`[1] "11:03" "10:04"`

`ISOdate(2023, 2, 1) # create a POSIXct date/time from component values`

`[1] "2023-02-01 12:00:00 GMT"`

Sometimes you need to read data containing a mixture of dates and date/times. R will normally set the result to `NA`

when the time field is missing. The following function will impute times of noon for such values.

```
<- function(x) {
toDateTime <- ifelse(is.na(x) | nchar(x) > 10, x, paste0(x, ' 12:00:00'))
x as.POSIXct(x, format='%Y-%m-%d %H:%M:%S')
}toDateTime(c(NA, '2023-02-01 11:03:00', '2023-03-01'))
```

```
[1] NA "2023-02-01 11:03:00 CST"
[3] "2023-03-01 12:00:00 CST"
```

## 3.4 Logical Operators

Logical intersection is denoted by `&`

and logical union by `|`

. Logical negation uses `!`

and use `!=`

for “not equal”. Here are examples.

```
<- 1 : 4
x <- c('cag', 'dog', 'cat', 'dog')
y > 2 x
```

`[1] FALSE FALSE TRUE TRUE`

`> 2 & y == 'dog' x `

`[1] FALSE FALSE FALSE TRUE`

`> 2 | y == 'dog' x `

`[1] FALSE TRUE TRUE TRUE`

`> 2 & y != 'dog' x `

`[1] FALSE FALSE TRUE FALSE`

`! (x > 2 & y != 'dog')`

`[1] TRUE TRUE FALSE TRUE`

There are special and and or operators for single values. `a && b`

returns `TRUE`

if both `a`

and `b`

are true but doesn’t bother to even evaluate `b`

if `a`

is false. For `a || b`

, `b`

is not evaluated if `a`

is `TRUE`

. When `b`

takes a while to compute, significant time savings can achieved.

## 3.5 Missing Values

R objects of any type can have elements whose values are missing. The symbol R uses for a missing value is `NA`

. The `is.na`

function returns `TRUE/FALSE`

according to whether an element is missing. The following examples illustrate operations on `NA`

s.

```
<- c(1, 2, NA, 4, 5, NA)
x mean(x) # mean of all x
```

`[1] NA`

`mean(x, na.rm=TRUE) # mean of non-missing x`

`[1] 3`

`is.na(x) # vector corresponding to x`

`[1] FALSE FALSE TRUE FALSE FALSE TRUE`

`sum(is.na(x)) # count # NAs`

`[1] 2`

`table(is.na(x)) # count # NAs and non-NAs`

```
FALSE TRUE
4 2
```

`! is.na(x)] # get the non-missing x's x[`

`[1] 1 2 4 5`

```
1] <- NA # make x[1] missing
x[ x
```

`[1] NA 2 NA 4 5 NA`

```
<- letters[1:6] # first 6 lower case letters of alphabet
y is.na(x)] # get y for which x is NA y[
```

`[1] "a" "c" "f"`

As seen in the examples, most simple statistical summarization functions such as `mean`

will result in `NA`

if any element is `NA`

, and you have to specify an optional argument `na.rm=TRUE`

to remove `NA`

s before computing so that the result will be, for example, the mean of the non-missing values.

## 3.6 Subscripting

Examples of subscripting are given above. Subscripting via placement of `[]`

after an object name is used for subsetting, and occasionally for using some elements more than once:

```
<- c('cat', 'dog', 'fox')
x 2:3] x[
```

`[1] "dog" "fox"`

`c(1, 1, 3, 3, 2)] x[`

`[1] "cat" "cat" "fox" "fox" "dog"`

Subscripting a variable or a data frame/table by a vector of `TRUE/FALSE`

values is a very powerful feature of R. This is used to obtain elements satisfying one or more conditions:

```
<- c(1, 2, 3, 2, 1, 4, 7)
x <- c(1, 8, 2, 3, 8, 9, 2)
y > 7] x[y
```

`[1] 2 1 4`

The last line of code can be read as “values of `x`

such that `y > 7`

”. The result is the same as the following.

`c(FALSE, TRUE, FALSE, FALSE, TRUE, TRUE, FALSE)] x[`

`[1] 2 1 4`

Negative subscripts mean “all but”:

`-c(1, 3, 5)] x[`

`[1] 2 2 4 7`

`-(6:7)] x[`

`[1] 1 2 3 2 1`

Elements of vectors can be named and elements can be retrieved by name.

```
<- c(cat=3, dog=1, giraffe=10, elephant=20)
w w
```

```
cat dog giraffe elephant
3 1 10 20
```

`'dog'] w[`

```
dog
1
```

`c('giraffe', 'elephant')] w[`

```
giraffe elephant
10 20
```

```
require(Hmisc)
.q(giraffe, elephant)] # Using Hmisc's .q function w[
```

```
giraffe elephant
10 20
```

```
<- c(3, 1, 10, 20)
w names(w) <- .q(cat, dog, giraffe, elephant)
w
```

```
cat dog giraffe elephant
3 1 10 20
```

```
<- structure(c(3, 1, 10, 20), names=.q(cat, dog, giraffe, elephant))
w w
```

```
cat dog giraffe elephant
3 1 10 20
```

## 3.7 `for`

Loops and Braces

Writing `for(...)`

creates a `for`

loop. Typically one loops over a sequence of integers, but the looping can be over the values of any vector. Here are examples, where we also introduce the `cat`

function for writing to the console. When the body of the loop takes more than one command, a series of commands may be inclosed in `{}`

.

`for(i in 1 : 5) cat('i=', i, ' ')`

`i= 1 i= 2 i= 3 i= 4 i= 5 `

`cat('\n') # finish a line started by cat() above (\nb = newline character)`

```
<- c(2.3, 1.7, 6.1)
x for(u in x) {
<- 1 + 2 * u
z cat(z, '')
}
```

`5.6 4.4 13.2 `

`cat('\n')`

`for`

loops frequently use subscripting as shown below.

```
<- c(1, 3, 7, 11)
x for(i in 1 : length(x)) cat('x[', i, ']=', x[i], ' ', sep='')
```

`x[1]=1 x[2]=3 x[3]=7 x[4]=11 `

`cat('\n')`

## 3.8 Branching and If/Then

### 3.8.1 Decisions Based on One Scalar Value

Common approaches to this problem are `if`

and `switch`

.

```
<- 'semiparametric'
type <- switch(type,
f parametric = ols(y ~ x),
semiparametric = orm(y ~ x),
nonparametric = rcorr(x, y, type='spearman'),
<- y / x
{ z c(median=median(z), gmean=exp(mean(log(z)))) } )
# The last 2 lines are executed for any type other than the 3 listed
<- if(type == 'parametric') ols(y ~ x)
f else
if(type == 'semiparametric') orm(y ~ x)
else
if(type == 'nonparametric') rcorr(x, y, type='spearman')
else {
<- y / z
z c(median=median(z), gmean=exp(mean(log(z)))
}
```

What is inside `if( )`

must be a single scalar element that is evaluated to whether it’s `TRUE`

or `FALSE`

.

### 3.8.2 Series of Separate Decisions Over a Vector of Values

The `ifelse`

or `data.table::fifelse`

functions are most often used for this, but `data.table::fcase`

is a little better. Here’s an example.

```
<- c('cat', 'dog', 'giraffe', 'elephant')
x <- ifelse(x %in% c('cat', 'dog'), 'domestic', 'wild')
type type
```

`[1] "domestic" "domestic" "wild" "wild" `

```
require(data.table)
fcase(x %in% c('cat', 'dog'), 'domestic', default='wild')
```

`[1] "domestic" "domestic" "wild" "wild" `

### 3.8.3 `if`

Trick

Sometimes when constructing variable-length vectors and other objects, elements are to be included in the newly constructed object only when certain conditions apply. When a condition does not apply, no element is to be inserted. We can capitalize on the fact that the result of `if(...)`

is `NULL`

when `...`

is not `TRUE`

, and concatenating `NULL`

results in ignoring it. Here are two examples. In the first the resulting vector will have length 2, 3, or 4 depending on `sex`

and `height`

. In the second example the new vector will have the appropriate element `names`

preserved.

```
<- 23; z <- 46; sex <- 'female'; height <- 71; u <- pi; w <- 7
y c(y, z, if(sex == 'male') u, if(height > 70) w)
```

`[1] 23 46 7`

`c(x1=3, if(sex == 'male') c(x2=4), if(height > 70) c(x3=height))`

```
x1 x3
3 71
```

```
# reduce clutter in case of variable name conflicts:
rm(y, z, sex, height, u, w)
```

## 3.9 Functions

There are so many functions in R that it may be better to use the stackoverflow.com Q&A to find the ones you need (as of 2022-05-26 there are 450,000 R questions there). Here are just a few of the multitude of handy R functions. The first functions listed below return the R missing value `NA`

if any element is missing. You can specify `na.rm=TRUE`

to remove `NA`

s from consideration first, so they will not cause the result to be `NA`

. Most functions get their arguments (inputs) in () after the function name. Some functions like `%in%`

are binary operators whose two arguments are given on the left and right of `%in%`

.

`mean`

,`median`

,`quantile`

,`var`

,`sd`

: Compute statistical summaries on one vector`cut2`

: function in the`Hmisc`

package to create a`factor`

variable from a numeric variable, grouping by quantile groups or to achieve a specific group size, with nice labeling of resulting`factor`

`levels`

`min, max`

: Minimum or maximum of values in a vector or of multiple variables, resulting in one number`pmin, pmax`

: Parallel minimum and maximum for vectors, resulting in a vector. Example:`pmin(x, 3)`

returns a vector of the same length as`x`

. Each element is the minimum of the original value or 3.`range`

: Returns a vector of length two with the minimum and maximum`seq`

: Generate a sequence of numbers`plot`

,`points`

,`lines`

,`text`

: Basic ploting functions`table`

: Frequency tabulation and multi-way tabulations of any type of vector variables`unique`

: Return vector of distinct values, in same order as original values`uniqueN`

: Number of distinct values in a vector or number of distinct data table rows (`uniqueN`

is in the`data.table`

package)`union`

,`intersect`

,`setdiff`

,`setequal`

: Set operations on two vectors (see below)`fintersect`

,`fsetdiff`

,`funion`

,`fsetequal`

: fast`data.table`

package versions of set functions`any`

,`all`

: Logical union and intersection`which`

: Compute integer subscripts corresponding to`TRUE`

values in a vector`a %in% b`

,`a %nin% b`

: Set membership functions that determine whether each element in`a`

is in`b`

(for`%in%`

) or is not in`b`

(for`%nin%`

, which is in the`Hmisc`

package)

Here are some examples of `seq`

.

`seq(4) # first 4 integers`

`[1] 1 2 3 4`

`seq(2, 9)`

`[1] 2 3 4 5 6 7 8 9`

`seq(1, 9, by=2)`

`[1] 1 3 5 7 9`

Set operators are amazingly helpful. Here are some examples.

```
unique(x) # vector of distinct values of x, including NA if occurred
sort(unique(x)) # distinct values in ascending order
setdiff(unique(x), NA) # distinct values excluding NA if it occurred
duplicated(x) # returns TRUE for elements that are duplicated by
# values occurring EARLIER in the list
union(x, y) # find all distinct values in the union of x & y
intersect(x, y) # find all distinct values in both x & y
setdiff(x, y) # find all distinct x that are not in y
setequal(x, y) # returns TRUE or FALSE depending on whether the distinct
# values of x and y are identical, ignoring how they
# are ordered
```

Find a list of subject ids that are found in baseline but not in follow-up datasets:

`<- setdiff(baseline$id, followup$id) idn `

Avoid repetition: Don’t say `if(animal == 'cat' | animal == 'dog') ....`

; use `%in%`

instead:

```
if(animal %in% c('cat', 'dog')) ...
# or if(animal %in% .q(cat, dog)) ... using Hmisc's .q
```

Likewise don’t say `if(animal != 'cat' & animal != 'dog')`

but use `if(animal %nin% c('cat', 'dog')) ...`

Here are examples of the use of the logical union and intersection functions, and `which`

.

```
<- c(1, 3, 7)
x <- c(7, 8, 9)
y any(x == 1)
```

`[1] TRUE`

`all(x == 1)`

`[1] FALSE`

`all(x > 0)`

`[1] TRUE`

`all(x > 0 & y > x)`

`[1] TRUE`

`which(x > 2)`

`[1] 2 3`

`which(x > 2 & y == 9)`

`[1] 3`

`which(x == 3 & y == 88)`

`integer(0)`

To get documentation on a function type the following in the R console: `?functionname`

or `?packagename::functionname`

.

### 3.9.1 Character Manipulation Functions

R has a large number of character/text manipulation functions, and several R packages extend this further, as demonstrated in Section 10.8. Consider commonly used built-in functions, all of which apply to individual strings and to vectors of strings.

`substring`

,`substr`

: fetch specific character numbers from strings; these also serve as left-hand-side functions to replace specific substrings in text`paste`

: combine strings using a specific separator character (default is a blank). This function has two forms: using`sep`

does parallel pasting keeping the same number of rows as the input arguments, or using`collapse`

to collapse all elements into a single scalar string.`paste0`

:`paste`

without`collapse`

and with the separator being the null string (`""`

or`''`

)`strsplit`

: split strings on specified delimiters that are found within the strings.`data.table`

has an enhanced version called`tstrsplit`

.`tolower`

,`toupper`

: convert a vector of strings to all lower case or all upper case respectively`grep`

,`grepl`

: find which elements of a string vector contain a given regular expression.`grep`

returns a vector of integer subscripts (an empty vector if no matches are found) and`grepl`

returns a vector of`TRUE/FALSE`

values that is the same length of the input vector.`sub`

: replace pieces of strings found by regular expression matching with a given string, or delete these pieces if the replacement string is a null character`gsub`

: like`sub`

but can handle multiple matches within a string

Here are some examples of `substr`

, `paste`

, and `strsplit`

.

```
<- c('dog', 'cat', 'catdog')
x substr(x, 2, 3)
```

`[1] "og" "at" "at"`

```
<- x
y substr(y, 2, 2) <- '?'
y
```

`[1] "d?g" "c?t" "c?tdog"`

`paste(x, ';', sep='')`

`[1] "dog;" "cat;" "catdog;"`

`paste0(x, ';')`

`[1] "dog;" "cat;" "catdog;"`

```
<- paste(x, collapse=' & ')
y y
```

`[1] "dog & cat & catdog"`

```
<- c(y, 'this & that')
y y
```

`[1] "dog & cat & catdog" "this & that" `

`strsplit(y, split=' & ')`

```
[[1]]
[1] "dog" "cat" "catdog"
[[2]]
[1] "this" "that"
```

`strsplit('aa,bb;cc', split=',|;') # regular expression ,|; means , or ;`

```
[[1]]
[1] "aa" "bb" "cc"
```

Regular expressions are mixtures of text and special characters that are used to find matches (R `grep`

and `grepl`

functions) or to find matches and change pieces of text (R `sub`

and `gsub`

functions). A regular expression may be plain text for which we seek an exact match, or may consist of various wild cards. Learning a bit about regular expressions, or learning how to figure them out on demand using an interactive tool such as `regexr.com`

opens up an amazing array of capabilities.

**Recommended Reading: General**

- RegexBuddy by Jan Goyvaerts
- Regular Expressions - An Introduction by Aivosto
- RegexOne by RegexOne (interactive)
- Introduction to Regular Expressions by Codular
- Regular Expression in Wikipedia
- List of Courses by Javin Paul

**Recommended Reading: R**

- Dealing with Regular Expressions by UC Business Analytics
- Regular Expressions by Roger Peng
- Regular Expressions in R by Rob Colautti
- R gsub by Endmemo

Special wild card symbols in regular expressions include the following basic ones.

Symbol | Meaning |
---|---|

`.` |
match any single character |

`?` |
require zero or one occurrence of the preceding element |

`*` |
require zero or more occurrences |

`+` |
require one or more occurrences |

A regular expression element of `.*`

means any number of any type of character. `?`

after an expression has a special meaning, e.g., `.*?`

results in “non-greedy” matching. Without `?`

, the resulting greedy matching would search for the longest character string that matched the expression. With `?`

after an expression, the earliest match is used.

Here are some regular expression examples using `grep`

, `grepl`

, and `gsub`

. Note that some elements of regular expressions have special meaning and must be escaped with `\\`

when you need to use them literally (or you can used `fixed=TRUE`

).

` x`

`[1] "dog" "cat" "catdog"`

`grep('cat', x) # find cat anywhere in strings`

`[1] 2 3`

`grepl('cat', x)`

`[1] FALSE TRUE TRUE`

`grep('^dog', x) # find dog if at the beginning (^) of the string`

`[1] 1`

`grep('cat$', x) # find cat if at the end of the string`

`[1] 2`

`grep('^cat$', x) # find cat if by itself in the string`

`[1] 2`

```
<- c('This happened.', 'The magic number was 42.', 'Maybe',
x 'His weight was 201', 't')
grep('[0-9]', x) # find strings containing a number
```

`[1] 2 4`

`grep('[[:digit:]]', x) # alternate approach`

`[1] 2 4`

`grep('a.p', x) # find a followed by any single character followed by p`

`[1] 1`

`grep('a\\.p', x) # find a followed by literal period followed by p`

`integer(0)`

`grep('.', x, fixed=TRUE) # fixed=TRUE -> not a regular expression, treat literally`

`[1] 1 2`

`grep('2[12]1', x) # find 2 followed by 1 or 2 followed by 1`

`integer(0)`

`grep('2[01]1', x) # find 2 followed by 0 or 1 followed by 1`

`[1] 4`

`grep('[w-z]', x) # find strings with w, x, y, or z in them`

`[1] 2 3 4`

`grep('[w-z]', x, invert=TRUE) # find strings without wxyz`

`[1] 1 5`

`grep('[[:punct:]]', x) # strings with punctuation characters`

`[1] 1 2`

`grep('[Tt]h.*?w', x) # T or t followed h then any number of characters followed by w`

`[1] 2`

```
# ? is to not allow "greedy" matching (find earliest match)
grep('T.*h', x) # T followed by zero or more letters followed by h
```

`[1] 1 2`

`grep('This|The', x) # find strings with This or The`

`[1] 1 2`

`gsub('Th', '??', x) # replace Th with ??`

```
[1] "??is happened." "??e magic number was 42."
[3] "Maybe" "His weight was 201"
[5] "t"
```

`gsub('Th.*? ', 'X', x) # replace anything between Th and a space with X`

```
[1] "Xhappened." "Xmagic number was 42." "Maybe"
[4] "His weight was 201" "t"
```

`gsub('Th(.*?) ', '\\1', x) # replace Th + anything + space with the anything`

```
[1] "ishappened." "emagic number was 42." "Maybe"
[4] "His weight was 201" "t"
```

`gsub('Th(.*?) .*', '\\1', x) # like previous but keep only what's between Th and space`

```
[1] "is" "e" "Maybe"
[4] "His weight was 201" "t"
```

The last two examples demonstrate the use of the `\\n`

notation for referring to the `n`

th part of the matching string that is within parentheses. The parentheses here are regular expression separators and not referring to input strings (to refer to parentheses in input strings reguires escaping the parentheses with `\\`

).

Here are some more regular expression examples, using `{a,b}`

notation. This requires the number of matches of the previous expression to be between `a`

and `b`

. For exactly `a`

matches use `{a}`

.

```
<- c('k', 'x1a', 'a12b', 'k13xxx98c', 'x456c', 'u1234v')
x grep('[a-z][0-9]{2}[a-z]', x)
```

`[1] 3 4`

`gsub('[a-z][0-9]{2}[a-z]', 'Z', x)`

`[1] "k" "x1a" "Z" "ZxZ" "x456c" "u1234v"`

```
# Convert m/dd/yyyy to yyyy-m-d and allow m, d to have 1-2 digits
<- c('3/27/1964', '12/24/1971', '3/2/2011', '12/241/1972')
x # Note: there are built-in R functions for date conversions
gsub('([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})', '\\3-\\1-\\2', x)
```

`[1] "1964-3-27" "1971-12-24" "2011-3-2" "12/241/1972"`

### 3.9.2 Writing Functions

Even new R users can benefit from writing functions to reduce repetitive coding. A function has *arguments* and these can have default values for when the argument is not specified by the user when the function is called. Here are some examples. One line functions do not need to have their bodies enclosed in `{}`

.

```
<- function(x) x ^ (1/3)
cuberoot cuberoot(8)
```

`[1] 2`

```
<- function(x, power=2) {
g <- abs(x - 0.5)
u / (1. + u ^ power)
u
}g(3, power=2)
```

`[1] 0.3448276`

`g(3)`

`[1] 0.3448276`

Write a function make `mean()`

drop missing values without our telling it.

`<- function(x) mean(x, na.rm=TRUE) mn `

Function to be used throughout the report to round fractional values by a default amount (here round to 0.001):

```
<- function(x) round(x, 3)
rnd # edit the 3 the change rounding anywhere in the report
```

A simple function to save coding when you need to recode multiple variables from 0/1 to no/yes:

`<- function(x) factor(x, 0:1, c('no', 'yes')) yn `

Even though functions described here returned simple results, many functions return complex tree-like objects (e.g., `list`

s). The most common example is a statistical model-fitting function that returns a “fit object” containing estimated values such as regression coefficients, standard errors, \(R^2\), etc.

### 3.9.3 Conditional Function Definition Trick

The fact that functions are objects in R creates many possibilities. One trick that has proven to simplify logic by reducing the number of `if()`

statements in the code is to define a function conditionally at the beginning, and using this generically-named function throughout the rest of the analysis. For example suppose that you define a variable specifying whether regression models are to be fitted in the usual parametric way, or fitted semiparametrically (i.e., in a way that is independent of how you transform the dependent variable as long as the transformation is order-preserving).

```
# Write all the possibilities for rmeth values and select one of them (semiparametric)
# If using 'semiparametric' we must load the rms package
<- c('semiparametric', 'parametric')[1]
rmeth <-
fitfunction switch(rmeth,
semiparametric = {require(rms); orm},
parametric = ols)
```

Later in the code:

```
<- fitfunction(y ~ x1 + x2)
f summary(f)
...<- fitfunction(y2 ~ age * sex) g
```

## 3.10 R Formula Language

R has a unified syntax for specification of statistical models. A model, or at least the major part of it, is specified by an R *formula object*, which is characterized by having `~`

in it. The formula is almost always the first argument to a model fitting function, e.g., you may specify a standard linear model using `lm(y ~ age + sex)`

. The formula syntax has several useful effects:

- Character and categorical (
`factor`

) variables are automatically expanded into the appropriate number of 0/1 indicator variables - An
`*`

in a formula automatically creates multiplicative interaction terms and adds lower-order terms (e.g., main effects). Though seldom used, you can also use`:`

to generate product terms if you want to include lower-order terms manually. - Parentheses in a formula can be used to factor out repetitive interactions
- Transformations (through function calls) can be part of formulas. Transformations can be 1-1, many-1, or 1-many:
- 1-1: take log or square root transformation on the fly
- many-1: convert several variables or a matrix into a single column (e.g., first principal component)
- 1-many: expand a single column into a matrix to represent polynomials, spline functions, harmonic series, etc.

The last feature is all-powerful, as expanding one continuous variable into a multi-column matrix allows one to estimate the transformation the variable needs to receive to optimally fit the data.

An R formula has a `~`

in it that separates the left-hand side (dependent variable(s)) from the right-hand side (independent variables). Independent variables that do not interact (act additively) are separated by `+`

. You can omit certain terms using the minus sign `-`

. The following examples will help you to learn how to use the formula language.

```
~ terms
response
~ age + sex # age + sex main effects
y ~ age + sex + age:sex # add second-order interaction
y ~ age*sex # second-order interaction +
y # all main effects
~ (age + sex + sbp)^2
y # age+sex+sbp+age:sex+age:sbp+sex:sbp
~ (age + sex + sbp)^2 - sex:sbp
y # all main effects and all 2nd order
# interactions except sex:sbp
~ (age + race)*sex # age+race+sex+age:sex+race:sex
y ~ treatment*(age*race + age*sex) # no interact. with race,sex
y sqrt(y) ~ sex*sqrt(age) + race
# functions, with dummy variables generated if
# race is an R factor (classification) variable
~ sex + poly(age,2) # poly generates orthogonal polynomials
y # poly(age,2) is a matrix with 2 columns
<- interaction(race,sex)
race.sex ~ age + race.sex # for when you want indicator variables for
y # all combinations of the factors
```

The `update`

function is handy for re-fitting a model with changes in terms or data:

```
<- lrm(y ~ rcs(x,4) + x2 + x3) # lrm, rcs in rms package
f <- update(f, subset=sex=="male")
f2 <- update(f, .~.-x2) # remove x2 from model
f3 <- update(f, .~. + rcs(x5,5))# add rcs(x5,5) to model
f4 <- update(f, y2 ~ .) # same terms, new response var. f5
```

## 3.11 Interactively Writing and Debugging R Code

The `RStudio`

IDE (integrated development environment) and the Microsoft `Visual Studio Code Editor`

are excellent tools for composing and debugging R code. With either you can run one command at a time from a script editor pane, or by entering or cutting and pasting commands into a console pane. You can also run `Quarto`

code chunks as a whole.

When you are not writing functions but are doing typical data analyses, a major component of coding involves using the correct variable names in formulas and other expressions. To facilitate that, it is useful to keep html output from `describe()`

or `contents()`

on the current working dataset in the `RStudio`

`View`

pane. This is achieved by running something like this in the console:

```
options(prType='html')
describe(d)
```

## 3.12 Resources for Learning R

- Catalog of resources on
`Stackoverflow`

- Ten Simple Rules for Teaching Yourself R
- Fast Lane to Learning R
- R Tutorials
- R Programming Tutorials
- R Bootcamp by Cole Beck
- Swirlstats (interactive)
- For those who have used SPSS or SAS before
- R books on Amazon
- UCLA site
- An Introduction to R
- R for Data Science
- Introduction to Data Science by Rafael Irizarry
- R in Action
- Statistical modeling by Legler and Roback
- stackoverflow.com/tags/r is the best place for asking questions about the language and for learning from answers to past questions asked
- Using example scripts
`.qmd`

(`Quarto`

) and`.Rmd`

(R Markdown) scripts here- Load one of these scripts easily into the
`RStudio`

script editor window and render an html report - Study the code to see what produced what, and start to understand the code
- Modify the code
- Example
- Go to the
`RStudio Console`

and run`require(Hmisc)`

then`getRs('stressecho.qmd', put='rstudio')`

- Click on
`Render`

to run the script and create an html report that is shown in the`RStudio`

`Viewer`

on the right

- Go to the
- Do likewise for scripts that work problems in
*The Analysis of Biological Data*, e.g.`abd17-lion.qmd`

,`abd18-molerats.qmd`