This lab focuses on creating functions: the why, the how, and some best practices. Much of this comes from the R for Data Science 2nd edition online text https://r4ds.hadley.nz/functions.html, conversations with CS faculty, and my experience (i.e. learn from my mistakes).
Directions (Please read before starting)
\(~\)
library(dplyr)
library(ggplot2)
\(~\)
\(~\)
This was the original code given where I asked you to remove the “magic numbers”
x <- c(1,2,3, "four", 5, "six", 7, 6, 2)
### START ###
x <- as.numeric(x)
# Get rid of NA values
x <- x[c(1,2,3,5,7,8,9)]
# Create a vector to add to x
y <- rep(1, length = 7)
# Create new data.frame with x, y, and x+y
df <- data.frame(x = x, y = y, z = x + y)
## Only keep values where z > 5 and x <= 7 and grab column "z"
z_new <- df[c(4,5,6), 3]
### END ###
z_new
## [1] 6 8 7
Most of you successfully turned this into code that looked like the following:
x <- c(1,2,3, "four", 5, "six", 7, 6, 2)
### START ###
x <- as.numeric(x)
# Get rid of NA values
x <- na.omit(x)
# Create a vector to add to x
y <- rep(1, length = length(x))
# Create new data.frame with x, y, and x+y
df <- data.frame(x = x, y = y, z = x + y)
## Only keep values where z > 5 and x <= 7 and grab column "z"
z_new <- subset(df, df$z>5 & df$x<=7)$z
### END ###
z_new
## [1] 6 8 7
Now let’s work on turning this into a function.
x <- c(1,2,3, "four", 5, "six", 7, 6, 2)
exampleFunction1=function(x){
x <- as.numeric(x)
x <- na.omit(x)# Get rid of NA values
y <- rep(1, length = length(x))# Create a vector to add to x
df <- data.frame(x = x, y = y, z = x + y)# Create new data.frame with x, y, and x+y
z_new <- subset(df, df$z>5 & df$x<=7)$z## Only keep values where z > 5 and x <= 7 and grab column "z"
z_new ## Print column "z"
}
exampleFunction1(x)
## [1] 6 8 7
x <- c(1,2,3, "four", 5, "six", 7, 6, 2)
exampleFunction2=function(x){
as.numeric(x)%>%
na.omit()%>% # Get rid of NA values
{.+1}%>% # Add 1
"["(.>5)%>% # Filter to greater than 5
"["(.<=8) #Filter to less than or equal to 8 (so x is <=7 since z=x+1)
}
exampleFunction2(x)
## [1] 6 8 7
x <- c(1,2,3, "four", 5, "six", 7, 6, 2)
exampleFunction3=function(x){
# exampleFunction:
# Expected input: Vector
# Expected output: Vector consisting of the input vector + 1 subject to constraints
# input vector <= 7
# output vector >5
outputVector<-as.numeric(x)%>%
na.omit()%>% # Get rid of NA values
{.+1}%>% # Add 1
"["(.>5)%>% # Filter to greater than 5
"["(.<=8) #Filter to less than or equal to 8 (so x is <=7 since z=x+1)
return(outputVector) #return the output
}
output=exampleFunction3(x)
print(output)
## [1] 6 8 7
x1 <- c(1,2,3, "four", 5, "six", 7, 6, 2) #base vector. Expected output: 6 8 7
x2 <- c(3, 7, "four", 2) #HW 1 vector. Expected output: 8
x3 <- c("a","b","c") #No Numbers. Expected output: None
x4 <- c(1,2,3,8,9,10) #All integers, none that should be printed (both above and below). Expected output: None
x5 <- c(4,5,6,7) #All integers, all should be printed. Expected output: 6 7 8
x6 <- c(1,2,3,4,5,6,7,8,9,10) #All integers, some should be printed. Expected output: 6 7 8
x7 <- c(5.1,4.1,3.9,7.1,6.8,55/10,75/10) #Non integer numbers. Expected output: 6.1 5.1 7.8 6.5
testVectors<-list(x1,x2,x3,x4,x5,x6,x7)
for (testVect in testVectors){ #we will work with Loops more on Thursday
print(exampleFunction3(testVect))
}
## [1] 6 8 7
## [1] 8
## numeric(0)
## numeric(0)
## [1] 6 7 8
## [1] 6 7 8
## [1] 6.1 5.1 7.8 6.5
\(~\)
At this point you should begin working with your partner. This lab
will continue building on the fundamental aspects of R
introduced previously. The lab’s examples will continue using the “Happy
Planet” data, so please make sure you include code to load it.
We will start with vector functions: Functions that take a vector or set of vectors and return a vector.
Question #1 Write a function that takes a vector, and then by element: squares the element and then adds 5. An example input and expected output are below:
## [1] 1.0 2.0 3.0 4.0 5.5
## [1] 6.00 9.00 14.00 21.00 35.25
Mutate functions are functions that work well inside mutate and filter because they return an output of the same length as the input. we’ve used z scores in a number of recent labs (e.g. Lab 7). Since this is something we have used multiple times, and will probably use again, it makes sense to create a function to do this.
zScore <- function(x) {
(x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)
}
Summarize functions are functions that return a single value for use in summarize(). For example, we could create a squared error function:
squaredError <- function(x) {
sum((x-mean(x))**2,na.rm=TRUE)
}
Question #2 Taken from the reading. Write bothNA(), a summary function that takes two vectors of the same length and returns the number of positions that have an NA in both vectors.
## [1] "Vector1: " "1" "NA" "3" "NA" "5"
## [7] "NA" "7" "6"
## [1] "Vector2: " "1" "2" "3" "NA" "5"
## [7] "NA" "7" "6" "2" "3" "7"
## [13] "NA" "2"
## [1] "Expected Output: " "4" "6"
Data frame functions work like dplyr verbs: they take a data frame as the first argument, some extra arguments that say what to do with it, and return a data frame or a vector.
Sometimes we need to use embracing. wrapping a variable in braces {} tells dplyr to use the value stored inside the argument not the argument as the variable name.
As an example, we seem to be doing a lot of dataframe manipulations that involve dropping na values and then combining 2 columns.
dropAndAdd<-function(df,col1,col2){
df=df%>%
filter(!is.na({{col1}}))%>%
filter(!is.na({{col2}}))%>%
mutate(sumCol1Col2={{col1}}+{{col2}})
return(df)
}
# Testing using airquality base R dataset
airquality2=dropAndAdd(airquality,Ozone,Solar.R)
airquality3<-airquality%>%
filter(!is.na(Ozone))%>%
filter(!is.na(Solar.R))%>%
mutate(sumCol1Col2=Ozone+Solar.R)
sum((airquality3==airquality2)==FALSE,na.rm=TRUE)
## [1] 0
Question #3 Using user defined functions Write a function that does the following
Hint: you should be able to do this with 2 pipes (3 total lines)
Question #4 Write a function that when given a dataset, an id column, and 2 variable columns does the following:
Note: Do this without calling any user-built functions. Figuring out the proper way to feed dataframe functions within a new dataframe function is outside the purview of this lab.
\(~\)
Plot functions take a dataframe and return a plot. For example, we’ve been making a significant number of histograms where the color and fill are based on the same variable, the colors are the same (so the borders match the fill), a minimal theme, and stacked bars.
basicHistogramPlot<-function(df,xCol,fillName,colorsToUse,alpha=0.5,bins=10){ #note that alpha and bins have defaults
outPlot=ggplot(data=df,aes(x={{xCol}},fill={{fillName}},color={{fillName}}))+
geom_histogram(position = "stack", alpha = alpha, bins=bins) +
scale_fill_manual(values = colorsToUse)+
scale_color_manual(values = colorsToUse)+
theme_minimal()
return(outPlot)
}
diamonds_data=diamonds
diamonds2 = diamonds_data%>%
filter(cut=='Fair')%>%
filter(clarity %in% c('VVS2','VVS1','IF'))%>%
filter(color %in% c('D','H','I','J'))
basicHistogramPlot(diamonds2,price,clarity,c("red","blue","green"),bins=2) #note that I overwrote the default bin, but not the default alpha
Question #5 Modify the above function to include a factor variable and title variables. Demonstrate that it works by recreating the goalplot from https://rebelskyw.cs.grinnell.edu/wp-content/uploads/2024/09/MatchingPlots.html excluding the axis break changes.