---
title: "Lec 05 - Lists, Attributes, & S3"
subtitle: "<br/> Statistical Programming"
author: "Sta 323 | Spring 2022"
date: "<br/> Dr. Colin Rundel"
output:
  xaringan::moon_reader:
    css: ["slides.css"]
    lib_dir: libs
    nature:
      highlightStyle: solarized-light
      highlightLines: true
      countIncrementalSlides: false
      ratio: "16:9"

---
exclude: true

```{r setup, message=FALSE, warning=FALSE, include=FALSE}
options(
  htmltools.dir.version = FALSE, # for blogdown
  width=80
)
```

---
class: middle


# Generic Vectors


---

## Lists

Lists are the other 1 dimensional (i.e. have a length) data structure in R, the different from atomic vectors in that they can contain a heterogeneous collection of R object (e.g. atomic vectors, functions, other lists, etc.).

```{r}
list("A", c(TRUE,FALSE), (1:4)/2, list(TRUE, 1), function(x) x^2)
```


---

## List Structure

Often we want a more compact representation of a complex object, the `str` function is useful for this, particularly for lists.

```{r}
str(c(1,2))
str(1:100)
str("A")
```

--

```{r}
str( list("A", c(TRUE,FALSE), (1:4)/2, list(TRUE, 1), function(x) x^2) )
```


---

## Recursive lists

Lists can contain other lists, meaning they don't have to be flat

```{r}
str( list(1, list(2, list(3, 4), 5)) )
```

.footnote[Because of this, lists become the most natural way of representing tree-like structures within R]

---

## List Coercion

By default a vector will be coerced to a list (as a list is more general) if needed

```{r}
str( c(1, list(4, list(6, 7))) )
```

--

We can coerce a list into an atomic vector using `unlist` - the usual type coercion rules then apply to determine the final type.

```{r}
unlist(list(1:3, list(4:5, 6)))
unlist( list(1, list(2, list(3, "Hello"))) )
```

.footnote[`as.intger` and similar functions can be used, but only if the list is flat (i.e. no lists inside your base list)]

---

## Named lists

Because of their more complex structure we often want to name the elements of a list (we can also do this with atomic vectors). 

This can make accessing list elements more straight forward.

```{r}
str(list(A = 1, B = list(C = 2, D = 3)))
```

--

More complex names need to be quoted,

```{r}
list("knock knock" = "who's there?")
```



---

## Exercise 1

Represent the following JSON data as a list in R.

```json
{
  "firstName": "John",
  "lastName": "Smith",
  "age": 25,
  "address": 
  {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": 10021
  },
  "phoneNumber": 
  [
    {
      "type": "home",
      "number": "212 555-1239"
    },
    {
      "type": "fax",
      "number": "646 555-4567"
    }
  ]
}
```

---
class: middle


# NULL Values

---

## `NULL`s

`NULL` is a special value within R that represents nothing - it always has length zero and type `"NULL"` and cannot have any attributes.

.pull-left[
```{r}
NULL
typeof(NULL)
mode(NULL)
length(NULL)
```
]

.pull-right[
```{r}
c()
c(NULL)
c(1, NULL, 2)
c(NULL, TRUE, "A")
```
]


.footnote[Note - If you're familiar with SQL, its `NULL` is more like R's `NA`]

---

## 0-length coercion

0-length length coercion is a special case of length coercion when one of the arguments has length 0. In this case the longer vector's length is not used and result will have length 0.

.pull-left[
```{r}
integer() + 1
log(numeric())
```
]

.pull-right[
```{r}
logical() | TRUE
character() > "M"
```
]

--

<br/>

As a `NULL` values always have length 0, this coercion rule will apply (note type coercion is also occurring here)

<div>
.pull-left[
```{r error=TRUE}
NULL + 1
```
<br/>
```{r error=TRUE}
log(NULL)
```
]

.pull-right[
```{r}
NULL | TRUE
NULL > "M"
```
]
</div>




---

## `NULL`s and comparison

Given the previous issue, comparisons and conditional with `NULL`s can be problematic.

```{r}
x = NULL
```

<br/>

```{r error=TRUE}
if (x > 0)
  print("Hello")
```

--

<br/>

```{r error=TRUE}
if (!is.null(x) & (x > 0))
  print("Hello")
```

--

<br/>

```{r error=TRUE}
if (!is.null(x) && (x > 0))
  print("Hello")
```


.footnote[This is due to short circuit evaluation which occurs with `&&` and `||` but not `&` or `|`.]

---
class: middle


# Attributes

---

## Attributes

Attributes are metadata that can be attached to objects in R. Some are special, e.g. `class`, `comment`, `dim`, `dimnames`, `names`, etc., as they change the way in which the object behaves.

--

Attributes are implemented as a named list that is attached to an object. They can be interacted with via the `attr` and `attributes` functions.

```{r}
(x = c(L=1,M=2,N=3))
```

--

.pull-left[
```{r}
attributes(x)
str(attributes(x))
```
]

.pull-right[
```{r}
attr(x, "names")
attr(x, "something")
```
]

---

## Assigning attributes

```{r}
names(x) = c("Z","Y","X")
x
```

--

```{r}
names(x)
```

--

```{r}
attr(x, "names") = c("A","B","C")
x
```

--

```{r}
names(x)
```


---

## Helpers functions vs attr

.pull-left[
```{r}
names(x) = 1:3
x
attributes(x)
```
]

.pull-right[
```{r}
names(x) = c(TRUE, FALSE, TRUE)
x
attributes(x)
```
]

--

<br/>

```{r}
attr(x, "names") = 1:3
x
attributes(x)
```


---

## Factors

Factor objects are how R represents categorical data (e.g. a variable where there are a fixed # of possible outcomes).

```{r}
(x = factor(c("Sunny", "Cloudy", "Rainy", "Cloudy", "Cloudy")))
```

--

```{r}
str(x)
```

--

```{r}
typeof(x)
mode(x)
```

---

## Composition

A factor is just an integer vector with two attributes: `class` and `levels`.

```{r}
x
attributes(x)
```

--

<br/>

We can build our own factor from scratch using,

```{r}
y = c(3L, 1L, 2L, 1L, 1L)
attr(y, "levels") = c("Cloudy", "Rainy", "Sunny")
attr(y, "class") = "factor"
y
```

---

## Building objects

The approach we just used is a bit clunky - generally the preferred method for construction an object with attributes from scratch is to use the `structure` function.

```{r}
y = structure(
  c(3L, 1L, 2L, 1L, 1L),
  levels = c("Cloudy", "Rainy", "Sunny"),
  class = "factor"
)

y
class(y)
is.factor(y)
```


.footnote[Named arguments become attributes]

---

Knowing factors are stored as integers help explain some of their more interesting behaviors:

```{r error=TRUE}
x+1
is.integer(x)
as.integer(x)
as.character(x)
as.logical(x)
```

---
class: middle


# S3 Object System

---

## `class`

The `class` attribute is an additional layer to R's type hierarchy,

<br/>

```{r echo=FALSE}
f = function(x) x^2
x = factor("A")
l = list(1, "A")
```

 value            |  `typeof()`      |  `mode()`      |  `class()`        
:-----------------|:-----------------|:---------------|:---------------
`TRUE`            | `r typeof(TRUE)` | `r mode(TRUE)` | `r class(TRUE)` 
`1`               | `r typeof(1)`    | `r mode(1)`    | `r class(1)`    
`1L`              | `r typeof(1L)`   | `r mode(1L)`   | `r class(1L)`   
`"A"`             | `r typeof("A")`  | `r mode("A")`  | `r class("A")`  
`NULL`            | `r typeof(NULL)` | `r mode(NULL)` | `r class(NULL)` 
`list(1, "A")`    | `r typeof(l)`    | `r mode(l)`    | `r class(l)`  
`factor("A")`     | `r typeof(x)`    | `r mode(x)`    | `r class(x)`  
`function(x) x^2` | `r typeof(f)`    | `r mode(f)`    | `r class(f)`
` `+` `           | builtin          | function       | function
` `[` `           | special          | function       | function

---

## S3 class specialization

```{r}
x = c("A","B","A","C")
```

--

```{r}
print( x )
```

--

```{r}
print( factor(x) )
```

--

```{r}
print( unclass( factor(x) ) )
```

--

```{r}
print.default( factor(x) )
```

--

```{r}
print
```

---

## Other examples

.pull-left[
```{r}
mean
t.test
```
]

.pull-right[
```{r}
summary
plot
```
]

<br/>

Not all base functions use this approach,

```{r}
sum
```

---

## What is S3?

<br/>

> S3 is R’s first and simplest OO system. It is the only OO system used in the base and stats packages, and it’s the most commonly used system in CRAN packages. S3 is informal and ad hoc, but it has a certain elegance in its minimalism: you can’t take away any part of it and still have a useful OO system.

>— Hadley Wickham, Advanced R

.footnote[
* S3 should not be confused with R's other object oriented systems: S4, Reference classes, and R6*.
]

---

## What's going on?

S3 objects and their related functions work using a very simple dispatch mechanism - a generic function is created whose sole job is to call the `UseMethod` function which then calls a class specialized function using the naming convention: `<generic>.<class>`

--

We can see all of the specialized versions of the generic using the `methods` function.

```{r}
methods("plot")
```

---

.small[
```{r}
methods("print")
```
]

---

```{r}
print.factor
```
---

```{r error=TRUE}
print.integer
```

--

.pad-top[]

```{r}
print.default
```

---

## The other way

If instead we have a class and want to know what specialized functions exist for that class, then we can again use the `methods` function with the `class` argument.

```{r}
methods(class="factor")
```

---

## Adding methods

```{r include=FALSE}
rm(print.x)
rm(print.y)
```

.pull-left[
```{r}
x = structure(c(1,2,3), class="class_A")
x
```
]

.pull-right[
```{r}
y = structure(c(6,5,4), class="class_B")
y
```
]

--

<div>
.pull-left[
```{r}
print.class_A = function(x) {
  cat("Class A!\n")
  print.default(unclass(x))
}

x
```
]

.pull-right[
```{r}
print.class_B = function(x) {
  cat("Class B!\n")
  print.default(unclass(x))
}

y
```
]
</div>

--

<div>
.pull-left[
```{r}
class(x) = "class_B"
x
```
]

.pull-right[
```{r}
class(y) = "class_A"
y
```
]
</div>

---

## Defining a new S3 Generic

```{r}
shuffle = function(x) {
  UseMethod("shuffle")
}
```

--

```{r}
shuffle.default = function(x) {
  stop("Class ", class(x), " is not supported by shuffle.\n", call. = FALSE)
}
```

--

```{r}
shuffle.factor = function(f) {
  factor( sample(as.character(f)), levels = sample(levels(f)) )
}

shuffle.integer = function(x) {
  sample(x)
}
```

--

.pull-left[
```{r error=TRUE}
shuffle( 1:10 )
shuffle( factor(c("A","B","C","A")) )
```
]

.pull-right[
```{r error=TRUE}
shuffle( c(1, 2, 3, 4, 5) )
shuffle( letters[1:5] )
```
]

---

## Exercise 2 - classes, modes, and types

Below we have defined an S3 method called `report`, it is designed to return a message about the type/mode/class of an object passed to it.

.pull-left[
```{r}
report = function(x) {
  UseMethod("report")
}

report.default = function(x) {
  "This class does not have a method defined."
}
```
]

.pull-right[
```{r}
report.integer = function(x) {
 "I'm an integer!"
}

report.double = function(x) {
  "I'm a double!"
}

report.numeric = function(x) {
  "I'm a numeric!"
}
```
]

<br/>

Try running the `report` function with different input types, what happens? 

Now run `rm("report.integer")` in your Console and try using the `report`
function again, what has changed? 

What does this tell us about S3, types, modes, and classes?

What if we also `rm("report.double")`?

---

## Conclusion?

From `UseMethod`s R documentation:

> If the object does not have a class attribute, it has an implicit class. Matrices and arrays have class "matrix" or "array" followed by the class of the underlying vector. Most vectors have class the result of `mode(x)`, except that integer vectors have class `c("integer", "numeric")` and real vectors have class `c("double", "numeric")`.

From Advanced R:

> How does UseMethod() work? It basically creates a vector of method names, paste0("generic", ".", c(class(x), "default")), and then looks for each potential method in turn.

--

<br/>

.center[
.large[Why?]

See https://twitter.com/WhyDoesR
]


