Part 1:

Write one paragraph each (in your own words) describing what are classes and one paragraph on what are data structures (with examples).

Classes:

In R, a class is a label that defines the data type of an object and helps R determine how different functions should behave when encountering it. All objects have a class attribute, whether it’s built-in or custom defined. When a function is called on an object, R checks its class and routes the call to the appropriate method, so the same function produces different, appropriate output depending on what kind of data it’s given. R has five basic atomic classes: numeric, logical, character, integer, and complex.

class(1)
## [1] "numeric"
class(TRUE)
## [1] "logical"
class("Character")
## [1] "character"
class(1L)
## [1] "integer"
class(1 + 1i)
## [1] "complex"

Data Structures:

Data structures in R are objects used to organize and store data in a specific format. R has six basic types of data structures: vectors, lists, matrices, data frames, arrays, and factors. Data structures can be single-dimensional or multidimensional. Single-dimensional structures represent data in a straight line or sequence. Multidimensional structures organize data across rows, columns, and higher-order axes. Homogeneous data structures can store only one class of data. Heterogeneous data structures can store multiple classes of data at the same time.

# Vectors are single-dimensional, homogeneous data structures.
vec_numeric <- c(1, 2, 3, 4, 5)
vec_logical <- c(TRUE, FALSE, TRUE, FALSE, FALSE)
vec_character <- c("Example", "Character", "Vector")
vec_integer <- c(1L, 2L, 3L, 4L, 5L)
vec_complex <- c(1 + 1i, 2 + 1i, 3 + 1i, 4 + 1i, 5 + 1i)
print(vec_numeric)
## [1] 1 2 3 4 5
print(vec_logical)
## [1]  TRUE FALSE  TRUE FALSE FALSE
print(vec_character)
## [1] "Example"   "Character" "Vector"
print(vec_integer)
## [1] 1 2 3 4 5
print(vec_complex)
## [1] 1+1i 2+1i 3+1i 4+1i 5+1i
# Lists are very similar to vectors but they are heterogeneous.
list_example <- list(c(1, 2, 3), TRUE, "List", 1L, 1 + 1i)
print(list_example)
## [[1]]
## [1] 1 2 3
## 
## [[2]]
## [1] TRUE
## 
## [[3]]
## [1] "List"
## 
## [[4]]
## [1] 1
## 
## [[5]]
## [1] 1+1i
# Matrices are two-dimensional, homogeneous data structures.
matrix_example <- matrix(1:6, nrow = 3, ncol = 2, byrow = TRUE)
print(matrix_example)
##      [,1] [,2]
## [1,]    1    2
## [2,]    3    4
## [3,]    5    6
# Data frames are two-dimensional, heterogeneous data structures.They are lists of vectors of equal lengths.
dataframe_example <- data.frame(
  Name = c("Alex Albon", "Kimi Antonelli", "Lewis Hamilton"), 
  Age = c(30, 20, 41),
  Q3_MadRing = c(FALSE, TRUE, TRUE)
)
print(dataframe_example)
##             Name Age Q3_MadRing
## 1     Alex Albon  30      FALSE
## 2 Kimi Antonelli  20       TRUE
## 3 Lewis Hamilton  41       TRUE
# Arrays are very similar to matrices, but they are able to contain more than two dimensions, and typically contain more general information.
array_example <- array(c(1:12), dim = c(2, 3, 2))
print(array_example)
## , , 1
## 
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]    2    4    6
## 
## , , 2
## 
##      [,1] [,2] [,3]
## [1,]    7    9   11
## [2,]    8   10   12
# Factors are very similar to vectors, but are specialized to store categorical data
factor_example <- factor(c("Category_1", "Category_2", "Category_3"))
print(factor_example)
## [1] Category_1 Category_2 Category_3
## Levels: Category_1 Category_2 Category_3

Pick a dataset, and apply two commands on your data. What do you find, and does it make sense? Please explain.

tooth_growth_data <- data.frame(datasets::ToothGrowth)
print(tooth_growth_data)
##     len supp dose
## 1   4.2   VC  0.5
## 2  11.5   VC  0.5
## 3   7.3   VC  0.5
## 4   5.8   VC  0.5
## 5   6.4   VC  0.5
## 6  10.0   VC  0.5
## 7  11.2   VC  0.5
## 8  11.2   VC  0.5
## 9   5.2   VC  0.5
## 10  7.0   VC  0.5
## 11 16.5   VC  1.0
## 12 16.5   VC  1.0
## 13 15.2   VC  1.0
## 14 17.3   VC  1.0
## 15 22.5   VC  1.0
## 16 17.3   VC  1.0
## 17 13.6   VC  1.0
## 18 14.5   VC  1.0
## 19 18.8   VC  1.0
## 20 15.5   VC  1.0
## 21 23.6   VC  2.0
## 22 18.5   VC  2.0
## 23 33.9   VC  2.0
## 24 25.5   VC  2.0
## 25 26.4   VC  2.0
## 26 32.5   VC  2.0
## 27 26.7   VC  2.0
## 28 21.5   VC  2.0
## 29 23.3   VC  2.0
## 30 29.5   VC  2.0
## 31 15.2   OJ  0.5
## 32 21.5   OJ  0.5
## 33 17.6   OJ  0.5
## 34  9.7   OJ  0.5
## 35 14.5   OJ  0.5
## 36 10.0   OJ  0.5
## 37  8.2   OJ  0.5
## 38  9.4   OJ  0.5
## 39 16.5   OJ  0.5
## 40  9.7   OJ  0.5
## 41 19.7   OJ  1.0
## 42 23.3   OJ  1.0
## 43 23.6   OJ  1.0
## 44 26.4   OJ  1.0
## 45 20.0   OJ  1.0
## 46 25.2   OJ  1.0
## 47 25.8   OJ  1.0
## 48 21.2   OJ  1.0
## 49 14.5   OJ  1.0
## 50 27.3   OJ  1.0
## 51 25.5   OJ  2.0
## 52 26.4   OJ  2.0
## 53 22.4   OJ  2.0
## 54 24.5   OJ  2.0
## 55 24.8   OJ  2.0
## 56 30.9   OJ  2.0
## 57 26.4   OJ  2.0
## 58 27.3   OJ  2.0
## 59 29.4   OJ  2.0
## 60 23.0   OJ  2.0
str(tooth_growth_data)
## 'data.frame':    60 obs. of  3 variables:
##  $ len : num  4.2 11.5 7.3 5.8 6.4 10 11.2 11.2 5.2 7 ...
##  $ supp: Factor w/ 2 levels "OJ","VC": 2 2 2 2 2 2 2 2 2 2 ...
##  $ dose: num  0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 0.5 ...
sapply(tooth_growth_data, is.numeric)
##   len  supp  dose 
##  TRUE FALSE  TRUE

str() shows $len and $dose are stored as numeric variables, and $supp is stored as a factor with two levels. This is confirmed by is.numeric, with $len and $dose marked as TRUE and $supp marked as FALSE. Tooth length is a continuous variable, so storing it as numeric makes sense. Supplement type is a categorical variable with two options (“OJ”, “VC”) so storing it as a two-level factor also makes sense. Despite being restricted to only three discrete choices (0.5, 1.0, 2.0), storing dosage as numeric makes sense as the underlying variable is still continuous.


Part II:

It is possible to open up certain functions and see the underlying math/operations and mechanics of the command. Open up two functions, and try to speculate their calculations.

function_IQR <- IQR
environment(function_IQR) <- globalenv()
print(function_IQR)
## function (x, na.rm = FALSE, type = 7) 
## diff(quantile(as.numeric(x), c(0.25, 0.75), na.rm = na.rm, names = FALSE, 
##     type = type))
function_sd <- sd
environment(function_sd) <- globalenv()
print(function_sd)
## function (x, na.rm = FALSE) 
## sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), 
##     na.rm = na.rm))

IQR calculates the Interquartile Range of a numeric vector. sd calculates the standard deviation of a numeric vector.

Write your own function.

farenheit_to_celcius <- function(f) {
  c <- (f - 32) * 5 / 9
  return(c)
}
farenheit_to_celcius(98.6)
## [1] 37

Part III:

Please explain Bayes’ Theorem in your own words, and give an example.

Bayes’ Theorem is a specific application of the Law of Conditional Probability that is used to update the probability of an event with the introduction of new evidence. The Law of Conditional Probability states that the probability of event A occurring, given that event B has already occurred, equals the probability of both A and B occurring together, divided by the probability of B occurring. It is written as:

\[P(A \mid B) = \frac{P(A \cap B)}{P(B)}\]

It can also used to calculate the probability of event B occurring after event A when written as:

\[P(B \mid A) = \frac{P(A \cap B)}{P(A)}\]

Reorganizing this equation as:

\[P(B \mid A) * P(A) = P(A \cap B)\]

allows us to use Bayes’ Theorem takes to updates the equation for the Law of Conditional Probability to calculate the posterior probability using the prior probabilities and the likelihood of observing the new evidence being introduced, written as:

\[P(A \mid B) = \frac{P(B \mid A) * P(A)}{P(B)}\]


Part IV:

Jose visits campus every Thursday evening. However, some days the parking garage is full, often due to college events. There are academic events on 35% of evenings, sporting events on 20% of evenings, and no events on 45% of evenings. When there is an academic event, the garage fills up about 25% of the time, and it fills up 70% of evenings with sporting events. On evenings when there are no events, it only fills up about 5% of the time. If Jose comes to campus and finds the garage full, what is the probability that there is a sporting event? Solve this using Bayes’ formula. Interpret the results. Then solve using a tree diagram.

A1 represents a sporting event, A2 an academic event, and A3 no event. B is that the parking garage is full.

\[P(A_{1}) = 0.2, P(A_{2}) = 0.35, P(A_{3}) = 0.45, P(B \mid A_{1}) = 0.7, P(B \mid A_{2}) = 0.25, P(B \mid A_{3}) = 0.05\]

Using Bayes’ Theorem, we can calculate the probability that there is a sporting event given that the garage is full.

\[P(A_{1} \mid B) = \frac{P(B \mid A_{1}) P(A_{1})}{P(B \mid A_{1}) P(A_{1}) + P(B \mid A_{2}) P(A_{2}) + P(B \mid A_{3}) P(A_{3})}\]

#Bayes' Theorem

# Given Probabilities
p_a1 <- 0.20
p_a2 <- 0.35
p_a3 <- 0.45
p_b_given_a1 <- 0.70
p_b_given_a2 <- 0.25
p_b_given_a3 <- 0.05

# Create Bayes' Formula given three events
bayes_three_events <- function(p_a1, p_a2, p_a3, p_b_given_a1, p_b_given_a2, p_b_given_a3){
  p_a1_given_b <- (p_b_given_a1 * p_a1) / ((p_b_given_a1 * p_a1) + (p_b_given_a2 * p_a2) + (p_b_given_a3 * p_a3))
  return(p_a1_given_b)
}

bayes_three_events(p_a1, p_a2, p_a3, p_b_given_a1, p_b_given_a2, p_b_given_a3)
## [1] 0.56
# Tree diagram
library(BiocManager)
library(Rgraphviz)
## Loading required package: graph
## Loading required package: BiocGenerics
## Loading required package: generics
## 
## Attaching package: 'generics'
## The following objects are masked from 'package:base':
## 
##     as.difftime, as.factor, as.ordered, intersect, is.element, setdiff,
##     setequal, union
## 
## Attaching package: 'BiocGenerics'
## The following objects are masked from 'package:stats':
## 
##     IQR, mad, sd, var, xtabs
## The following objects are masked from 'package:base':
## 
##     anyDuplicated, aperm, append, as.data.frame, basename, cbind,
##     colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
##     get, grep, grepl, is.unsorted, lapply, Map, mapply, match, mget,
##     order, paste, pmax, pmax.int, pmin, pmin.int, Position, rank,
##     rbind, Reduce, rownames, sapply, saveRDS, table, tapply, unique,
##     unsplit, which.max, which.min
## Loading required package: grid
# Given probabilities
p_a1 <- 0.20
p_a2 <- 0.35
p_a3 <- 0.45
p_b_given_a1 <- 0.70
p_b_given_a2 <- 0.25
p_b_given_a3 <- 0.05

# Create nodes
n1 <- "Probability"
n2 <- "Sporting Event"
n3 <- "Academic Event"
n4 <- "No Event"
n5 <- "SE - Full"
n6 <- "SE - Vacancy"
n7 <- "AE - Full"
n8 <- "AE - Vacancy"
n9 <- "NE - Full"
n10 <- "NE - Vacancy"

n_labels <- c(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10)

# Create directed graph
rEG <- new(
  "graphNEL", 
  nodes = n_labels, 
  edgemode = "directed"
  )

# Create edges
rEG <- addEdge(n1, n2, rEG, 1)
rEG <- addEdge(n1, n3, rEG, 1)
rEG <- addEdge(n1, n4, rEG, 1)
rEG <- addEdge(n2, n5, rEG, 1)
rEG <- addEdge(n2, n6, rEG, 1)
rEG <- addEdge(n3, n7, rEG, 1)
rEG <- addEdge(n3, n8, rEG, 1)
rEG <- addEdge(n4, n9, rEG, 1)
rEG <- addEdge(n4, n10, rEG, 1)

# Name and label edges
edge_names <- edgeNames(rEG)
edge_labels <- c(
  p_a1,
  p_a2,
  p_a3,
  p_b_given_a1,
  1 - p_b_given_a1,
  p_b_given_a2,
  1 - p_b_given_a2,
  p_b_given_a3,
  1 - p_b_given_a3
)

edge_labels <- format(edge_labels, trim = TRUE, nsmall = 2)

names(edge_labels) <- edge_names

# Edge attributes
eAttrs <- list(
  label = edge_labels,
  fontsize = setNames(rep(24, length(edge_names)), edge_names),
  color = setNames(rep("blue", length(edge_names)), edge_names)
)

# Graph and node attributes
gAttrs <- list(
  node = list(
    fillcolor = "pink",
    fontsize = "14"
  ),
  graph = list(
    rankdir = "LR"
  )
)

# Plot tree diagram
probability.tree <- plot(
  rEG,
  edgeAttrs = eAttrs,
  attrs = gAttrs
)

# Calculate probability of a sporting event given a full parking garage using outcome probabilities
p_a1_and_b <- p_a1 * p_b_given_a1
p_a2_and_b <- p_a2 * p_b_given_a2
p_a3_and_b <- p_a3 * p_b_given_a3
p_b <- p_a1_and_b + p_a2_and_b + p_a3_and_b
p_a1_given_b <- p_a1_and_b / p_b
p_a1_given_b
## [1] 0.56