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.
#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
This workshop aims to develop and master our skills in data visualisation using ggplot2
#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
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
#ggplot(data = mpg)
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))
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy))
# the + should be on top line
# 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
#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'
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'
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))
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.
ggplot(data = diamonds) +
stat_summary(
mapping = aes(x = cut, y = depth),
fun.min = min,
fun.max = max,
fun = median
)
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")
The Developer’s Toolbox & AI
#install.packages("ellmer")
# install.packages("chattr")
library(ggplot2)
data(iris)
ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, color = Species)) +
geom_point()
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
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
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)
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