Introduction

This code contains the work realised during the first module of MB5370.

It is structured at follow: - Workshop 1 - Foundations Focus on the comprehension of key principles of R, including its ecosystem, syntax/scripts, packages, data types, data structures, R projects architecture, Qmd/Rmd and sourcing scripts. * Note that the code related to this workshop is in a single chunk because it was firt written in an R script then transfered in this Rmd script. - Workshop 2 - Visualisation Focuss on the development of visuals to investigate datasets using ggplot, how to face troubleshooting, and optinmisations of visuals, including the integration of statistical details on plots. - Workshop 4 - The Developer’s Toolbox & AI Learn how to use GitHub Copilot and conversational contexts (chattr) inside RStudio, as well as to build scalable, automated and error proff functions using functional iterations.

Setup

Workshop 1

#source("../MB5370/MB5370_W1.R")
#----------------------------------------------#
#MB5370: Introduction to programming
#Darius HUISMAN
# May 2026

#Workspace: 
# ~/Desktop/MB5370/MB5370_W1.R
#----------------------------------------------#

#----------------------------------------------#
# Workshop 01. Introduction ####

# Getting started ####
# This section introduces us to R by using it as a simple calculator.

# Variables and Assignment ####
# This section focuses on understanding how data is stored in R and why that matters.

age <- 25
first_name <- 'Bill'

Age <- 41 # cases matter

  # Exercise ####
a <- 15 + 25.1 + 20.25
b <- 26
c <- a + b

# Functions ####

years_old <- 25.7
round(years_old) # rounds up
## [1] 26
floor(years_old) # rounds down
## [1] 25
years_old <- 25.765
round (years_old, 2) # comma after the object to specify argument
## [1] 25.76
?round # go to help

args(round) # use args in the Console
## function (x, digits = 0, ...) 
## NULL
  # Exercise 
my_age <- 26
my_name <- 'Darius'

?paste # go to help

paste(my_name, "is", my_age, "years old.")
## [1] "Darius is 26 years old."
# Debugging ####

# Misconceptions
# variables in programs do not work the same way as they do in spreadsheets
grade <- 55
total <- grade + 10
print (total)
## [1] 65
grade <- 90
print (total) # value of total in a spreadsheet will be 100, but in programming a variable holds the value it was assigned (65)
## [1] 65
total <- grade + 10
print (total) # executed in the way it was defined
## [1] 100
# What will this code do?
p <- 2
z <- 5
out <- p * z  # What should the value of out be?
print (out) # What is the value of out? Is it the same as expected?
## [1] 10
# Testing
x <- 1
is.character(x)
## [1] FALSE
is.numeric (x)
## [1] TRUE
  # Exercise 
#my_quiz <- c("uno",
#             "dos",
#             "tres",
#             "cuatro" # missing a ",", causing an error
#             "cinco")
#print (my_quis) # syntax error, should be "my_quiz"
#str(my_quiz)
#len(my_quiz) # wrong function / function does not exist

# errors fixed
my_quiz <- c("uno",
             "dos",
             "tres",
             "cuatro", # corrected
             "cinco")
print (my_quiz) # corrected 
## [1] "uno"    "dos"    "tres"   "cuatro" "cinco"
str(my_quiz)
##  chr [1:5] "uno" "dos" "tres" "cuatro" "cinco"
length(my_quiz) # corrected
## [1] 5
# Data types ####

  #Exercise
#numeric
my_numeric_variable = 4.2 
class(my_numeric_variable)
## [1] "numeric"
#character
my_name <- 'Darius'
class(my_name)
## [1] "character"
#integer
int <- 1L
class(int)
## [1] "integer"
#logical
t <- TRUE
class(t)
## [1] "logical"
# Data structure ####

# Vector
y <- c(1, 2, 3)
z <- c("Sarah", "Tracy", "Jon")

  # Exrecise
class(x)
## [1] "numeric"
class(y)
## [1] "numeric"
## both are numeric

# List
x <- list(1, "a", TRUE)
x
## [[1]]
## [1] 1
## 
## [[2]]
## [1] "a"
## 
## [[3]]
## [1] TRUE
x[[2]]
## [1] "a"
# Data frame and tibbles
my_data_frame <- data.frame(no = c(1,2,3), name = c("Tracey", "John", "Pete"), "True or False" = c(TRUE, FALSE, TRUE))
my_data_frame
##   no   name True.or.False
## 1  1 Tracey          TRUE
## 2  2   John         FALSE
## 3  3   Pete          TRUE
str (my_data_frame)
## 'data.frame':    3 obs. of  3 variables:
##  $ no           : num  1 2 3
##  $ name         : chr  "Tracey" "John" "Pete"
##  $ True.or.False: logi  TRUE FALSE TRUE
my_data_frame$no = as.factor(my_data_frame$no)
str (my_data_frame)
## 'data.frame':    3 obs. of  3 variables:
##  $ no           : Factor w/ 3 levels "1","2","3": 1 2 3
##  $ name         : chr  "Tracey" "John" "Pete"
##  $ True.or.False: logi  TRUE FALSE TRUE
# Packages and Libraries ####

# install.packages('tidyverse') # download and install
library(tidyverse) # load into current workspace
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.2.1     ✔ readr     2.2.0
## ✔ forcats   1.0.1     ✔ stringr   1.6.0
## ✔ ggplot2   4.0.3     ✔ tibble    3.3.1
## ✔ lubridate 1.9.5     ✔ tidyr     1.3.2
## ✔ purrr     1.2.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# install.packages ('ggplot2') # comment out so it doesn’t run again

# install.packages("ggplot","tidyr", "earthtones","redlistr","dplyr" ,"terra")

library (ggplot2)
?ggplot2

# Coding best practices ####

# load packages
library(ggplot2)
library(tidyr)
library(tidyverse)

  # Class discussion
    # 1. Read the .csv dataset into R using the 'read_csv' function
    # 2. Check the data file donwloaded (make sure the file has been read correctly and spot potential data that could cause problem)
    # 3. Analyse the abundance of each species
    # 4. PLot the species abundance of the species of you want 

Workshop 2

This workshop aims to develop and master our skills in data visualisation using ggplot2

2.16 Load packages and data

#install.packages("tidyverse")
library(tidyverse)
library(ggplot2)
mpg
## # A tibble: 234 × 11
##    manufacturer model      displ  year   cyl trans drv     cty   hwy fl    class
##    <chr>        <chr>      <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
##  1 audi         a4           1.8  1999     4 auto… f        18    29 p     comp…
##  2 audi         a4           1.8  1999     4 manu… f        21    29 p     comp…
##  3 audi         a4           2    2008     4 manu… f        20    31 p     comp…
##  4 audi         a4           2    2008     4 auto… f        21    30 p     comp…
##  5 audi         a4           2.8  1999     6 auto… f        16    26 p     comp…
##  6 audi         a4           2.8  1999     6 manu… f        18    26 p     comp…
##  7 audi         a4           3.1  2008     6 auto… f        18    27 p     comp…
##  8 audi         a4 quattro   1.8  1999     4 manu… 4        18    26 p     comp…
##  9 audi         a4 quattro   1.8  1999     4 auto… 4        16    25 p     comp…
## 10 audi         a4 quattro   2    2008     4 manu… 4        20    28 p     comp…
## # ℹ 224 more rows

2.17 Create your first ggplot

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy))

2.18 Understand the ‘grammar of graphics’

2.18.1 Graphing template

#ggplot(data = mpg)

2.18.2 Aesthetic mappings

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, colour = class))

#change point size by class
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, size = class))
## Warning: Using size for a discrete variable is not advised.

#> Warning: Using size for a discrete variable is not advised.

#change point transparency by class
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, alpha = class))
## Warning: Using alpha for a discrete variable is not advised.

#change point shape by class
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, shape = class))
## Warning: The shape palette can deal with a maximum of 6 discrete values because more
## than 6 becomes difficult to discriminate
## ℹ you have requested 7 values. Consider specifying shapes manually if you need
##   that many of them.
## Warning: Removed 62 rows containing missing values or values outside the scale range
## (`geom_point()`).

# all points blue
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy), color = "blue")

#distinct displ data < 5
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy, color = displ < 5))

2.19 Troubleshooting

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = hwy)) 

# the + should be on top line

2.20 Facet and panel plots

# facet_wrap
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_wrap(~ class, nrow = 2)

# facet_grid
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_grid(drv ~ cyl)

#using a .
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) + 
  facet_grid(. ~ cyl)

# it does not make a plot for each hwy (compared to previous plot)

Read ?facet_wrap. What does nrow do? What does ncol do? What other options control the layout of the individual panels?

?facet_wrap

nrow, ncol = number of rows and columns you want in the grid

2.21 Fitting simple lines

#display data as point
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy))

#display same data as smooth line through the points
ggplot(data = mpg) + 
  geom_smooth(mapping = aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#with different dashes for drv
ggplot(data = mpg) + 
  geom_smooth(mapping = aes(x = displ, y = hwy, linetype = drv))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#use group() argument
ggplot(data = mpg) +
  geom_smooth(mapping = aes(x = displ, y = hwy, group = drv))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#Change the color of each line based on drv value
ggplot(data = mpg) +
  geom_smooth(
    mapping = aes(x = displ, y = hwy, color = drv),
    show.legend = FALSE,
  )
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#plot multiple geom() at the same time
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy)) +
  geom_smooth(mapping = aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#more efficent code
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_point() + 
  geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#style the points by themself
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_point(mapping = aes(color = class)) + 
  geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

#use a filter (class = "subcompact") to select a subset of the data and plot only that subset
ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_point(mapping = aes(color = class)) + 
  geom_smooth(data = filter(mpg, class == "subcompact"), se = FALSE)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Exercise

1.What geom would you use to draw a line chart? A boxplot? A histogram? An area chart? geom_line() geom_boxplot() geomp_histogram() geom_area()

2.Run this code in your head and predict what the output will look like. Then, run the code in R and check your predictions. Will these two graphs look different? Why/why not? ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + geom_point() + geom_smooth()

ggplot() + geom_point(data = mpg, mapping = aes(x = displ, y = hwy)) + geom_smooth(data = mpg, mapping = aes(x = displ, y = hwy))

the two plots will provide with the same output. They will display the data with both points and smooth information at the smae time

ggplot(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_point() + 
  geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

ggplot() + 
  geom_point(data = mpg, mapping = aes(x = displ, y = hwy)) + 
  geom_smooth(data = mpg, mapping = aes(x = displ, y = hwy))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

2.22 Transformations and Stats

2.22.1 Plotting statistics

ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut))

#recreate the previous plot using stat_count() instead of geom_bar()
ggplot(data = diamonds) + 
  stat_count(mapping = aes(x = cut))

2.22.2 Overriding defaults in ggplot2

demo <- tribble(
  ~cut,         ~freq,
  "Fair",       1610,
  "Good",       4906,
  "Very Good",  12082,
  "Premium",    13791,
  "Ideal",      21551
)
demo
## # A tibble: 5 × 2
##   cut        freq
##   <chr>     <dbl>
## 1 Fair       1610
## 2 Good       4906
## 3 Very Good 12082
## 4 Premium   13791
## 5 Ideal     21551
ggplot(data = demo) +
  geom_bar(mapping = aes(x = cut, y = freq), stat = "identity")

#override a default mapping from transformed variables to aesthetics
ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut, y = stat(prop), group = 1))
## Warning: `stat(prop)` was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(prop)` instead.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

#> Warning: `stat(prop)` was deprecated in ggplot2 3.4.0.

2.22.3 Plotting statistical details

ggplot(data = diamonds) + 
  stat_summary(
    mapping = aes(x = cut, y = depth),
    fun.min = min,
    fun.max = max,
    fun = median
  )

2.23 Aesthetic adjustments

ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut, colour = cut))

ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut, fill = cut))

#colour by another variable like clarity
ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut, fill = clarity))

#use position = "identity"
#To alter transparency (alpha)
ggplot(data = diamonds, mapping = aes(x = cut, fill = clarity)) + 
  geom_bar(alpha = 1/5, position = "identity")

#To color the bar outlines with no fill color
ggplot(data = diamonds, mapping = aes(x = cut, colour = clarity)) +
  geom_bar(fill = NA, position = "identity")

#position = "fill" 
ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut, fill = clarity), position = "fill")

#position = "dodge"
ggplot(data = diamonds) + 
  geom_bar(mapping = aes(x = cut, fill = clarity), position = "dodge")

#position = "jitter"
ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy), position = "jitter")

Workshop 4

The Developer’s Toolbox & AI

4.4 chattr (Conversational Workspace Engine)

#install.packages("ellmer")
# install.packages("chattr")

Exercise: Prompting for Context

library(ggplot2)

data(iris)

ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, color = Species)) +
  geom_point()

4.3 Conditional Logic - filtering your data

Base R: if and else

# Simple Example: Checking Sea Surface Temperature (SST)
sst <- 30.2

if (sst > 29.5) {
  print("Warning: Marine heatwave threshold exceeded!")
} else {
  print("SST remains within baseline parameters.")
}
## [1] "Warning: Marine heatwave threshold exceeded!"

Tidyverse Alternative: Vectorised Conditionals (dplyr)

if_else()

# Load tidyverse (if not already loaded)
library(tidyverse)

# Sample coral data
coral_monitoring <- tibble(
  site = c("Site_A", "Site_B", "Site_C"),
  depth_m = c(12, 35, 8)
)

# Classify depth using if_else
coral_monitoring <- coral_monitoring %>% 
  mutate(zone = if_else(depth_m > 30, "Deep Reef", "Shallow Reef"))

what mutate() does? The mutate() function, from the dplyr package, is used to add new variables (columns) to a data frame or modify existing ones. It always operates on a data frame and returns a new data frame with the added or modified columns, while preserving existing columns.

what abou the %>% symbol? The %>% symbol is known as the pipe operator. It comes from the magrittr package, which is loaded as part of the tidyverse. Its primary purpose is to pass the result of the expression on its left-hand side as the first argument to the function on its right-hand side. This allows you to chain multiple operations together in a more readable and sequential manner, making your code flow from left to right, rather than nesting functions inside each other.

case_when()

coral_monitoring <- coral_monitoring %>% 
  mutate(reef_category = case_when(
    depth_m < 10  ~ "Lagoon / Flats",
    depth_m <= 30 ~ "Crest / Slope",
    depth_m > 30  ~ "Mesophotic / Deep",
    TRUE          ~ "Unclassified" # Catch-all remainder
  ))

task

library(dplyr)

marine_stations <- tibble(
  salinity = c(35, 28, 32, 12)
) %>%
  mutate(
    environment_type = case_when(
      salinity < 15 ~ "Estuarine",
      salinity >= 15 & salinity <= 30 ~ "Brackish",
      salinity > 30 ~ "Marine",
      TRUE ~ NA_character_ # Optional: handles any other cases not explicitly covered
    )
  )

print(marine_stations)
## # A tibble: 4 × 2
##   salinity environment_type
##      <dbl> <chr>           
## 1       35 Marine          
## 2       28 Brackish        
## 3       32 Marine          
## 4       12 Estuarine

4.4 Automation — Iterating Efficiently

Base R: for-loops

# Classic loop anatomy
for (year in 2020:2024) {
  print(paste("Processing climate data for year:", year))
}
## [1] "Processing climate data for year: 2020"
## [1] "Processing climate data for year: 2021"
## [1] "Processing climate data for year: 2022"
## [1] "Processing climate data for year: 2023"
## [1] "Processing climate data for year: 2024"
# Loop across index sequences
transect_lengths <- c(50, 100, 25, 75)

for (i in seq_along(transect_lengths)) {
  print(paste("Transect number", i, "measures", transect_lengths[i], "metres."))
}
## [1] "Transect number 1 measures 50 metres."
## [1] "Transect number 2 measures 100 metres."
## [1] "Transect number 3 measures 25 metres."
## [1] "Transect number 4 measures 75 metres."

Tidyverse Alternative: Functional Programming (purrr) Comparison Example: Calculating Square Roots of Site Vectors Using a standard for-loop:

site_areas <- c(144, 400, 625)
results <- numeric(length(site_areas)) # Must build an empty container first

for(i in seq_along(site_areas)) {
  results[i] <- sqrt(site_areas[i])
}

Using the purrr equivalent:

library(purrr)
# Map directly and define your expected output type explicitly
mapped_results <- map_dbl(site_areas, sqrt)

Iterating Across Complex Groups

# Iterate a summary function over split lists of data
iris %>% 
  split(.$Species) %>% 
  map(~summary(.x))
## $setosa
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.300   Min.   :1.000   Min.   :0.100  
##  1st Qu.:4.800   1st Qu.:3.200   1st Qu.:1.400   1st Qu.:0.200  
##  Median :5.000   Median :3.400   Median :1.500   Median :0.200  
##  Mean   :5.006   Mean   :3.428   Mean   :1.462   Mean   :0.246  
##  3rd Qu.:5.200   3rd Qu.:3.675   3rd Qu.:1.575   3rd Qu.:0.300  
##  Max.   :5.800   Max.   :4.400   Max.   :1.900   Max.   :0.600  
##        Species  
##  setosa    :50  
##  versicolor: 0  
##  virginica : 0  
##                 
##                 
##                 
## 
## $versicolor
##   Sepal.Length    Sepal.Width     Petal.Length   Petal.Width          Species  
##  Min.   :4.900   Min.   :2.000   Min.   :3.00   Min.   :1.000   setosa    : 0  
##  1st Qu.:5.600   1st Qu.:2.525   1st Qu.:4.00   1st Qu.:1.200   versicolor:50  
##  Median :5.900   Median :2.800   Median :4.35   Median :1.300   virginica : 0  
##  Mean   :5.936   Mean   :2.770   Mean   :4.26   Mean   :1.326                  
##  3rd Qu.:6.300   3rd Qu.:3.000   3rd Qu.:4.60   3rd Qu.:1.500                  
##  Max.   :7.000   Max.   :3.400   Max.   :5.10   Max.   :1.800                  
## 
## $virginica
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.900   Min.   :2.200   Min.   :4.500   Min.   :1.400  
##  1st Qu.:6.225   1st Qu.:2.800   1st Qu.:5.100   1st Qu.:1.800  
##  Median :6.500   Median :3.000   Median :5.550   Median :2.000  
##  Mean   :6.588   Mean   :2.974   Mean   :5.552   Mean   :2.026  
##  3rd Qu.:6.900   3rd Qu.:3.175   3rd Qu.:5.875   3rd Qu.:2.300  
##  Max.   :7.900   Max.   :3.800   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    : 0  
##  versicolor: 0  
##  virginica :50  
##                 
##                 
## 

Task using chattr:

library(purrr) # For map_dbl()

# List of fish count data
fish_counts_list <- list(
  site_a = c(10, 12, 8, 15, 11),
  site_b = c(25, 20, 22, 18),
  site_c = c(5, 7, 6)
)

# Using a for-loop
mean_counts_for_loop <- vector("numeric", length = length(fish_counts_list))
names(mean_counts_for_loop) <- names(fish_counts_list)

for (i in seq_along(fish_counts_list)) {
  mean_counts_for_loop[i] <- mean(fish_counts_list[[i]])
}

print("Mean counts (for-loop):")
## [1] "Mean counts (for-loop):"
print(mean_counts_for_loop)
## site_a site_b site_c 
##  11.20  21.25   6.00
# Using purrr's map_dbl()
mean_counts_map_dbl <- map_dbl(fish_counts_list, mean)

print("Mean counts (map_dbl):")
## [1] "Mean counts (map_dbl):"
print(mean_counts_map_dbl)
## site_a site_b site_c 
##  11.20  21.25   6.00

4.5 Writing custom functions

Function Construction Blueprint

# Function Definition
calculate_coral_mortality <- function(initial_count, surviving_count) {
  
  # Logic safety switch using our conditional tools!
  if (initial_count <= 0) {
    stop("Initial count must be greater than zero.")
  }
  
  mortality_rate <- (initial_count - surviving_count) / initial_count
  return(mortality_rate)
}

# Utilizing your custom function
calculate_coral_mortality(initial_count = 120, surviving_count = 84)
## [1] 0.3

Student Exercise 4: Custom Function Construction Task

#create a function to convert temperature in C° to F°
convert_temp_c_to_f <- function(temp_C){
  #make a safety check to prevent temperatures below absolute zero
  if (temp_C < -273.15){
    stop("Entered value is below absolute zero (-273.15 degrees Celsius)")
  }
  temp_F <- (temp_C * 9/5) + 32
  return(temp_F)
}
#test function
convert_temp_c_to_f(temp_C = 25)
## [1] 77
#convert_temp_c_to_f(temp_C = -300)

4.6 Advanced Scripting Extension Challenges

Challenge 1: Nested Multi-Vector Iteration

survey_counts <- c(15, 24, 8, 42)
species_specific_scaling_factors <- c(1.2, 0.8, 2.5, 1.1)

purrr::map2_dbl(survey_counts,species_specific_scaling_factors, `*`)
## [1] 18.0 19.2 20.0 46.2

Challenge 2: The Robust Data Wrangling Pipeline Function

#Create a custom function
clean_and_classify_survey <- function(df, col_name, threshold_parameter){
  #Safely check if the specified column name exists
  if (!col_name %in% names(df)){
    stop("Entered column ", col_name, " not found.")
  }
  #filter out NA values and mutate based on threshold
  cleaned_df <- df %>%
                  filter_out(is.na(!!sym(col_name))) %>%
                          mutate(status_classification = case_when(
                            col_name < threshold_parameter ~ "Fail",
                            col_name >= threshold_parameter ~ "Pass"
                          ))
  return(cleaned_df)
}

#tests
#1
test1 <- tibble(Names = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J"),
                Grade = c(56, 78, 42, 90, 87, 38, 67, 75, 2, 52))
clean_and_classify_survey(test1, "Grade", 50)
## # A tibble: 10 × 3
##    Names Grade status_classification
##    <chr> <dbl> <chr>                
##  1 A        56 Pass                 
##  2 B        78 Pass                 
##  3 C        42 Pass                 
##  4 D        90 Pass                 
##  5 E        87 Pass                 
##  6 F        38 Pass                 
##  7 G        67 Pass                 
##  8 H        75 Pass                 
##  9 I         2 Pass                 
## 10 J        52 Pass
#2 (NA values)
test2 <- tibble(Names = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J"),
                Grade = c(56, 78, 42, 90, NA, 38, 67, 75, 2, NA))
clean_and_classify_survey(test2, "Grade", 50)
## # A tibble: 8 × 3
##   Names Grade status_classification
##   <chr> <dbl> <chr>                
## 1 A        56 Pass                 
## 2 B        78 Pass                 
## 3 C        42 Pass                 
## 4 D        90 Pass                 
## 5 F        38 Pass                 
## 6 G        67 Pass                 
## 7 H        75 Pass                 
## 8 I         2 Pass