{r} setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE) library(graph) library(Rgraphviz)
#I. R Nuts and Bolts/Programming Readings.
Data Types
There are 5 main data types used within R: Numeric (Double, Integer), Character, Logical, Complex and Raw.
Numeric is pretty straightforward. They are simply numbers. Stored as Double Precision by default. Meaning that numbers are recorded with decimal and floating-point precision. THis includes whole numbers, typically Integers by default unless spcified. Integrers can be stored/ID’ed by adding an ‘L’ after the number itself (62L).
a <- 32
a
## [1] 32
is.numeric(a)
## [1] TRUE
Characters are also very simple. THey are a string of characters, but the key identifier is that they are encased within double quotes (“ABC”). If the double quotes are missing, then they will be considered a string.
b <- "Hello, is this thing on?"
b
## [1] "Hello, is this thing on?"
is.character(b)
## [1] TRUE
Logical is like asking R a question. Where the answer is either TRUE or FALSE. Also called Boolen, logical data is often used for comparisons.
3>1
## [1] TRUE
22==2.2
## [1] FALSE
Complex data includes numbers that have real and imaginary parts. The imaginary i is used whenever dealing with taking the square root of a negative number.
c <- 5+4i
is.complex(c)
## [1] TRUE
And lastly we have Raw data. Instead of storing text as regular numbers or characters, they’re stored as the memory unit bytes. These aren’t regularly used in stats work. It is mainly binary data.
Data Structures
There are 4 main types of data strucures: Vector, Matrix/Array, Dataframe and a List.
The simpliest is a Vector. It is when you have 1 or more numbers in a 1 dimensional array (all in a straight line or a row) that are all of the same data type. A vector is R’s most basic data structure
d <- c(3, 5, 2)
d
## [1] 3 5 2
is.vector(d)
## [1] TRUE
Next is the Matrix or Array. They are a collection of vectors that are stored in 2 dimensions. Having rows and columns. Similar to vectors, all the columns of a matrix need to be of the same length and the data needs to be of the same type. A key identifier between vectors and a matrix is how their contents are labeled. Vectors have ssqaure brackets [#] with it’s object indexing number inside. Where as a matrix will have these brackets to ID the rows [#, ] and the columns like so [ , #].
An array is just like a matrix, but instead of being in 2 dimensions, it has 3.
e <- c(1:16)
e
## [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
matrix(e,4)
## [,1] [,2] [,3] [,4]
## [1,] 1 5 9 13
## [2,] 2 6 10 14
## [3,] 3 7 11 15
## [4,] 4 8 12 16
f <- 17:32
f
## [1] 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
array(c(e,f), dim = c(4,4,2))
## , , 1
##
## [,1] [,2] [,3] [,4]
## [1,] 1 5 9 13
## [2,] 2 6 10 14
## [3,] 3 7 11 15
## [4,] 4 8 12 16
##
## , , 2
##
## [,1] [,2] [,3] [,4]
## [1,] 17 21 25 29
## [2,] 18 22 26 30
## [3,] 19 23 27 31
## [4,] 20 24 28 32
A Dataframe will have 2 dimensions of data. Where often its made up of a series of vectors, which can contain varrying types of data types. The important thing is that all the vectors need to have the same length. DFs are considered the easiest structure to work with and akin to a spreadsheet.
mydataframe <- data.frame(e,f)
mydataframe
## e f
## 1 1 17
## 2 2 18
## 3 3 19
## 4 4 20
## 5 5 21
## 6 6 22
## 7 7 23
## 8 8 24
## 9 9 25
## 10 10 26
## 11 11 27
## 12 12 28
## 13 13 29
## 14 14 30
## 15 15 31
## 16 16 32
Lastly there are lists. THey are an ordered collection of elements. THe elements can be of any type, length and/or structure. Making it the most flexiable data structure but also one of the most tricky to work with. Lists can even contain another list within it.
listA <- list("A", "C", "M", "E")
listA
## [[1]]
## [1] "A"
##
## [[2]]
## [1] "C"
##
## [[3]]
## [1] "M"
##
## [[4]]
## [1] "E"
listB <- list(listA, 4L, "ever")
listB
## [[1]]
## [[1]][[1]]
## [1] "A"
##
## [[1]][[2]]
## [1] "C"
##
## [[1]][[3]]
## [1] "M"
##
## [[1]][[4]]
## [1] "E"
##
##
## [[2]]
## [1] 4
##
## [[3]]
## [1] "ever"
I have picked the Data set “Nile.” It is a collection of annual water-flow measurements on the Nile River. The data on the 1 time series variable that is numeric, specifically a double precision numeric variable. The class() command told me it was a time series, which lines up with the given description of it being a yearly measurement. Time series should have numeric values. The typeof() command helped ID what type of numeric values we have. Glancing at the data, it is seemingly whole numbers so it could be integer, but typeof() disproves that.
data("Nile")
str(Nile)
## Time-Series [1:100] from 1871 to 1970: 1120 1160 963 1210 1160 1160 813 1230 1370 1140 ...
summary(Nile)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 456.0 798.5 893.5 919.4 1032.5 1370.0
class(Nile)
## [1] "ts"
typeof(Nile)
## [1] "double"
#II. Reading and Writing Functions in R
Still using the Nile data set, I used median() to figure out the averge of the time series data. This method of finding the average takes the middle of all the cases and uses it as the anchor of which the rest of the values spread out from. Being a series of numeric values, measuring the amount of water flow, median would be a strong choice to determine the average amount if there are extreme outliers.
median
## function (x, na.rm = FALSE, ...)
## UseMethod("median")
## <bytecode: 0x7ff175be70b8>
## <environment: namespace:stats>
median(Nile)
## [1] 893.5
The second function I looked at was Inter Quantile Range IQR. It finds the range of the middle 50% of values, branching off 25% in each direction from the median. The R function for it seems to start off taking the given data (x) and not remove any missing values. Then using the diff() function to calculate the difference from the 25 percentile (0.25) to the 75th percentile (0.75). The quantile function is what takes the concate() function of the 0.25 and 0.75 and turns it into the desired quantiles. For the data set, we get 234, meaning from the median of 893.5, the bulk of the data extends out 117 on either side.
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))
## <bytecode: 0x7ff1348c82b0>
## <environment: namespace:stats>
IQR(Nile)
## [1] 234
For my function I wanted to find the top 10% of values in the data set. I started off using the quantile function I saw in the IQR function and set it to 0.9 for the top 10%. But that only gave me back the value at that specific percentile, not all the values within the top 10%. So I made a new function starting with Nile calling on itself, but only returning values that are greater than or equal to the 90 percentile.
Top10 <- quantile(Nile, 0.9)
Top10
## 90%
## 1160
Top10percent <- Nile[Nile >= quantile(Nile, 0.9)]
Top10percent
## [1] 1160 1210 1160 1160 1230 1370 1180 1210 1250 1260 1220 1170
Bayes rule deals with probabilities. Data is often subject to change. When dealing with a subject matter that is currently evolving, the data or known probabilities of outcomes shift. If we are too rigorous and unable to adjust our calculations, our predictions will be stuck using out of date info, leading to sub optimal decisions. Bayes rule allows us to tweak outcome probabilities after we learn new information. Built off of the Lawe of Conditional Probability, which is deals with the likelihood of an event occurring, AFTER a certain other event has taken place. Bayes rule gives us the ability to adjust the second events’ probability with the new info at hand.
\[ P(A \mid B) = \frac{P(B \mid A)P(A)}{P(B)} \] An example could be you trying to figure out the likelihood of a video game character’s special move doing critical damage or not. For every normal attack, there’s a x chance that it does the bonus critical damage. You have your base level character stats, giving you the intial probabilities for each attack. But then lets say you get a new peice of gear and it boosts your ‘luck’ stat, making it more likely that you will perform that extra crit damage. Bayes rule would allow us to update our equaltion on determining on the likelihood of performing that cirt damage with this new equipment.
I started off by creating variables for each given value, as well as the cumulative probabilities that make up the likelihood of a full garage, regardless of what event or non event is happening (Prob B). This made it much easier to track what was what.
PA1_Sporting <- 0.2
PB_A1_Sporting <- 0.7
PA2_Acaademic <- 0.35
PB_A2_Academic <- 0.25
PA3_NoEvent <- 0.45
PB_A3_NoEvent <- 0.05
PB_Full <- (PB_A1_Sporting * PA1_Sporting) + (PB_A2_Academic * PA2_Acaademic) + (PB_A3_NoEvent * PA3_NoEvent)
PA1_B_Full_for_Sporting <- (PB_A1_Sporting * PA1_Sporting) / PB_Full
PA1_B_Full_for_Sporting
## [1] 0.56
To make the data tree, I started off with the graph and Rgraphviz packages, then I created all the nodes/leaves. The nodes were combined into a single vector, nodeNames, as to easily apply commands to all of them.
#nodes
node1 <- "Parking Garage"
node2 <- "A1 Sporting"
node3 <- "A2 Academic"
node4 <- "A3 No Event"
node5 <- "B_A1 Sporting Full"
node6 <- "A1 Sporting Open"
node7 <- "B_A2 Academic Full"
node8 <- "A2 Academic Open"
node9 <- "B_A3 No Event Full"
node10 <- "A3 No Event Open"
nodeNames <- c(
node1, node2, node3, node4, node5, node6, node7, node8, node9, node10)
ParkingTree <- graph::graphNEL(
nodes = nodeNames,
edgemode = "directed"
)
From there, i moved onto adding all the branches, connecting the nodes. It was a matter of using the addEdge() and adjusting the index numbers from the nodeNames variable to get the particular branches I wanted.
ParkingTree <- graph::addEdge(nodeNames[1], nodeNames[2], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[1], nodeNames[3], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[1], nodeNames[4], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[2], nodeNames[5], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[2], nodeNames[6], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[3], nodeNames[7], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[3], nodeNames[8], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[4], nodeNames[9], ParkingTree, 1)
ParkingTree <- graph::addEdge(nodeNames[4], nodeNames[10], ParkingTree, 1)
To get the probability values on the branches I needed to use eAttr() to make a list. That list housed the labeling info for each branch. Finding the node names, then adding the given text value for each one.
eAttrs <- list()
eAttrs$label <- c(
"Parking Garage~A1 Sporting" = "0.2",
"Parking Garage~A2 Academic" = "0.35",
"Parking Garage~A3 No Event" = "0.45",
"A1 Sporting~B_A1 Sporting Full" = "0.7",
"A1 Sporting~A1 Sporting Open" = "0.3",
"A2 Academic~B_A2 Academic Full" = "0.25",
"A2 Academic~A2 Academic Open" = "0.75",
"A3 No Event~B_A3 No Event Full" = "0.05",
"A3 No Event~A3 No Event Open" = "0.95"
)
But I still needed to add the end leaves probabilities. Since theres no further branches I needed to make use of text positioning and creating probability variables, just like the ones from earlier.
SportingFull <- 0.2 * 0.7
SportingOpen <- 0.2 * 0.3
AcademicFull <- 0.35 * 0.25
AcademicOpen <- 0.35 * 0.75
NoEventFull <- 0.45 * 0.05
NoEventOpen <- 0.45 * 0.95
The end leaf probabilities will come after plotting the tree. When I was changing the text sizes, the layout of the tree went from a vertical to horizontal orientation, making me completely redo the text positions. I found the dev.off() function to remove any rendered visualizations made all the troubleshooting much easier to see if things were rendering correctly.
And here is the text positioning. The first 2 numeric values are the x and y coordinates.
methods::selectMethod("plot", c("graph", "missing"))(
ParkingTree,
edgeAttrs = eAttrs,
attrs = list(
edge = list(fontsize = "24")
)
)
##
## 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
text(35, 0, SportingFull, cex = .8)
text(120, 0, SportingOpen, cex = .8)
text(210, 0, AcademicFull, cex = .8)
text(295, 0, AcademicOpen, cex = .8)
text(385, 0, NoEventFull, cex = .8)
text(475, 0, NoEventOpen, cex = .8)