# INBO CODING CLUB 27 June 2024 Welcome! ## Share your code snippet If you want to share your code snippet, copy paste your snippet within a section of three backticks (```): As an **example**: ``` library(tidyverse) ``` (*you can copy paste this example and add your code further down*) ## Yellow sticky notes No yellow sticky notes online. Put your name + " | " and add a "*" each time you solve a challenge (see below). ## Participants Name | Challenges --- | --- Damiano Oldoni | *** Pieter Huybrechts | *** Nele Mullens | ** Ward Standaert | ** Rhea Maesele | * Dirk Maes | Dirk Festraets | Jo-Hannes Nowé |** Heleen Deroo | Sanne Govaert | ** Siebe Indestege | ** Anja Leyman | * Soria Delva | * Tina Tuerlings | *** Katrijn Alaerts | ** Lotte Pohl | * Lucia Manzanares | * ## Challenge 1 ### Damiano's solution (example) Copy paste this section to show your solutions. ```r # dummy code a <- 1 print("This is how to insert text which looks like code.") ``` ### Heleen's solution ```` map(swiss, mean) map(iris, class) list_with_dfs <- list(swiss = swiss, iris = iris) map(list_with_dfs, nrow) map_dbl(swiss, mean) map_chr(iris, class) map_dbl(list_with_dfs, nrow) map_df(swiss, mean) map_df(iris, class) map_df(list_with_dfs, nrow) ```` ### Nele's solution ``` #1.1 Compute the mean of every column in the swiss data.frame. #swiss is a basic package in R base, just like iris map(swiss, mean) #1.2 Determine the type of each column in the iris data.frame. map(iris, class) #1.3 After creating the variable list_with_dfs as a named list with swiss and # iris (code provided), compute the number of rows of each data.frame. list_with_dfs <- list(iris = iris, swiss = swiss) #list with dataframes map(list_with_dfs, nrow) #1.4 map() ALWAYS returns a list, ALWAYS. Apply the right map_*() variants to # the previous exercises to return a numeric vector (exercises 1 and 3), a # character vector (exercise 2) or a data.frame (exercises 1, 2 and 3). Hint: # use cheat sheet or the sections map-atomic and the-map-functions. map_vec(swiss, mean) map_vec(list_with_dfs, nrow) map_chr(iris, class) map_df(swiss, mean) map_df(iris, class) map_df(list_with_dfs, nrow) ``` ### Jo-Hannes' solution ``` ## CHALLENGE 1 #Write map() statements to: #1. map(swiss, mean) #2. map(iris,typeof) #3. list_with_dfs <- list(swiss = swiss, iris = iris) map(list_with_dfs,nrow) #4. map_dbl(swiss,mean) map_dbl(list_with_dfs,nrow) map_chr(iris,typeof) map_df(swiss,mean) map_df(iris,typeof) map_df(list_with_dfs,nrow) ``` ### Siebe's Solution ``` # 1.1 map(swiss, mean) # 1.2 map(iris, class) # 1.3 list_with_dfs <- list(swiss, iris) names(list_with_dfs) <- c("swiss","iris") map(list_with_dfs, nrow) # 1.4 map_dbl(swiss, mean) map_dbl(list_with_dfs, nrow) map_chr(iris,class) map_df(swiss, mean) map_df(iris, class) map_df(list_with_dfs, nrow) ``` ## Challenge 2 ### Sanne's solution ```r # 2.1 map(sensors, read_sensor_data) # 2.2 sensor_dfs <- map( sensors, read_sensor_data, path_pattern = path_pattern_datafiles, extension = file_extension ) # 2.3 map2(means, st_dev, rnorm, n = 10) %>% str() # 2.4 datetimes <- function(sensor_df) { summarize( sensor_df, min_datetime = min(datetime), max_datetime = max(datetime) ) } map_df(sensor_dfs, datetimes) ``` ### Ward's solution ```r # 2.1 # Define the list with the sensor IDs sensors <- list("A1", "G7", "H4") names(sensors) <- sensors #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' read_sensor_data(sensor = "H4") read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0("./data/20240627/20240627_sensor_", sensor, ".txt") readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1") sensor_list <- map(sensors, \(x) read_sensor_data(x)) # 2.2 #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @param path_pattern character. The path common to all sensor files. #' @param extension character. The file extension starting with `.`. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' path_pattern_datafiles <- "./data/20240627/20240627_sensor_" #' file_extension <- ".txt" #' read_sensor_data(sensor = "H4", path_pattern_datafiles, file_extension) read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0(path_pattern, sensor, extension) readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # If data files are where they should be, here the path_pattern and # extension to use. Just copied from the @example above the function. path_pattern_datafiles <- "./data/20240627/20240627_sensor_" file_extension <- ".txt" # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1", path_pattern = path_pattern_datafiles, extension = file_extension ) map(sensors, read_sensor_data, path_pattern = path_pattern_datafiles, extension = file_extension) # 2.3 # Define means and standard deviations means <- c(-10, 0, 10, 20) st_dev <- c(1, 3, 2, 1.5) map2(means, st_dev, rnorm, n = 10) # 2.4 max_min_fun <- \(x) summarize(x, min_datetime = min(datetime), max_datetime = max(datetime)) map_df(sensor_list, max_min_fun) ``` ### Jo-Hannes' solution ```r # 2.1 # Define the list with the sensor IDs sensors <- list("A1", "G7", "H4") names(sensors) <- sensors #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' read_sensor_data(sensor = "H4") read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0("./data/20240627/20240627_sensor_", sensor, ".txt") readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1") sensor_data <- map(sensors,read_sensor_data) # 2.2 Now, you want to generalize your script #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @param path_pattern character. The path common to all sensor files. #' @param extension character. The file extension starting with `.`. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' path_pattern_datafiles <- "./data/20240627/20240627_sensor_" #' file_extension <- ".txt" #' read_sensor_data(sensor = "H4", path_pattern_datafiles, file_extension) read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0(path_pattern, sensor, extension) readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # If data files are where they should be, here the path_pattern and # extension to use. Just copied from the @example above the function. path_pattern_datafiles <- "./data/20240627/20240627_sensor_" file_extension <- ".txt" # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1", path_pattern = path_pattern_datafiles, extension = file_extension ) sensor_data <- map(sensors, \(x) read_sensor_data(x,path_pattern= path_pattern_datafiles, extension=file_extension)) # 2.3 # Define means and standard deviations means <- c(-10, 0, 10, 20) st_dev <- c(1, 3, 2, 1.5) #for one example rnorm(n=10, mean = -10, sd=1) #map to all elements map2(means,st_dev, \(x,y) rnorm(n=10,mean=x,sd=y)) # 2.4 #sensor_data is a list of the sensor data.frames #Try it for one example summarize(sensor_data[[1]], min_datetime = min(datetime), max_datetime = max(datetime)) #map it to all elements of the list (so each dataframe) map(sensor_data, \(x) summarize(x, min_datetime = min(datetime), max_datetime = max(datetime))) # 2.3 # Define means and standard deviations means <- c(-10, 0, 10, 20) st_dev <- c(1, 3, 2, 1.5) #for one example rnorm(n=10, mean = -10, sd=1) #map to all elements map2(means,st_dev, \(x,y) rnorm(n=10,mean=x,sd=y)) # 2.4 #sensor_data is a list of the sensor data.frames #Try it for one example summarize(sensor_data[[1]], min_datetime = min(datetime), max_datetime = max(datetime)) #map it to all elements of the list (so each dataframe) map(sensor_data, \(x) summarize(x, min_datetime = min(datetime), max_datetime = max(datetime))) ``` ### Nele's solution ```r # 2.1 # Define the list with the sensor IDs sensors <- list("A1", "G7", "H4") names(sensors) <- sensors #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' read_sensor_data(sensor = "H4") read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0("./data/20240627/20240627_sensor_", sensor, ".txt") readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1") all_sensors <- map(sensors, read_sensor_data) # 2.2 #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @param path_pattern character. The path common to all sensor files. #' @param extension character. The file extension starting with `.`. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' path_pattern_datafiles <- "./data/20240627/20240627_sensor_" #' file_extension <- ".txt" #' read_sensor_data(sensor = "H4", path_pattern_datafiles, file_extension) read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0(path_pattern, sensor, extension) readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # If data files are where they should be, here the path_pattern and # extension to use. Just copied from the @example above the function. path_pattern_datafiles <- "./data/20240627/20240627_sensor_" file_extension <- ".txt" # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1", path_pattern = path_pattern_datafiles, extension = file_extension ) map(sensors, read_sensor_data, path_pattern_datafiles, file_extension) # er zijn drie argumenten in de functie, de eerste vult ze vanzelf in, de tweede # en derde niet, deze moeten na de functie (hier read_sensor_data) zelf nog in- # gevuld worden # 2.3 Calculate 10 random numbers from a normal distribution (rnorm(n = 10)) for # each pair of the 4 provided means and standard deviations and return it as a list of 4 # Define means and standard deviations means <- c(-10, 0, 10, 20) st_dev <- c(1, 3, 2, 1.5) map2(means, st_dev, function(x, y) rnorm(n = 10, mean = x, sd = y)) # 2.4 # get sensor data ready sensor_data <- map(sensors, read_sensor_data, path_pattern_datafiles, file_extension) # we need to get a function to do what we want function(x) summarize(x, min_datetime = min(datetime), max_datetime = max(datetime)) # apply the function, output a dataframe min_max_time <- map_df(sensor_data, function(x) summarize(x, min_datetime = min(datetime), max_datetime = max(datetime))) # but I want to see what dates are from what sensor. Add names: min_max_time <- map2_df(sensor_data, sensors, function(x, sensors) summarize(x, sensor = sensors, min_datetime = min(datetime), max_datetime = max(datetime))) ``` ### Pieter's solution ```r # 2.1 # Define the list with the sensor IDs sensors <- list("A1", "G7", "H4") names(sensors) <- sensors #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' read_sensor_data(sensor = "H4") read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0("./data/20240627/20240627_sensor_", sensor, ".txt") readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1") ### 2.1 map(sensors, read_sensor_data) # 2.2 #' Function to read a comma separated sensor data file. #' #' @param sensor character. A sensor ID. #' @param path_pattern character. The path common to all sensor files. #' @param extension character. The file extension starting with `.`. #' @return A tibble data.frame as returned by `readr::read_csv()`. #' @examples #' # example code #' library(readr) #' path_pattern_datafiles <- "./data/20240627/20240627_sensor_" #' file_extension <- ".txt" #' read_sensor_data(sensor = "H4", path_pattern_datafiles, file_extension) read_sensor_data <- function( sensor, path_pattern, extension) { path <- paste0(path_pattern, sensor, extension) readr::read_csv(path, na = "NA", col_types = cols( datetime = col_datetime(format = "%d/%m/%Y %H:%M:%S") ) ) } # If data files are where they should be, here the path_pattern and # extension to use. Just copied from the @example above the function. path_pattern_datafiles <- "./data/20200630/20200630_sensor_" file_extension <- ".txt" # Apply function to one sensor ID as check. sensor_df <- read_sensor_data(sensor = "A1", path_pattern = path_pattern_datafiles, extension = file_extension ) all_sensor_dfs <- map( sensors, ~ read_sensor_data(.x, path_pattern = "./data/20240627/20240627_sensor_", extension = ".txt" ) ) map( sensors, read_sensor_data, path_pattern = "./data/20240627/20240627_sensor_", extension = ".txt" ) # 2.3 # Define means and standard deviations means <- c(-10, 0, 10, 20) st_dev <- c(1, 3, 2, 1.5) map2(means, st_dev, \(mean_to_calc, sd_to_calc) rnorm(n = 10, mean = mean_to_calc, sd = sd_to_calc) ) # 2.4 get_min_max_dtime <- function(df) { summarize(df, min_datetime = min(datetime), max_datetime = max(datetime)) |> ungroup() } all_sensor_dfs |> map(get_min_max_dtime) ``` ## Challenge 3 ### Sanne's solution ```r # 3.1 lepidoptera_in_prot_areas <- readRDS("./data/20240627/20240627_lepidoptera_in_prot_areas.RData") map(lepidoptera_in_prot_areas, length) %>% str() # 3.2 obs <- readr::read_tsv("./data/20240627/20240627_lepidoptera_2024.tsv", na = "") slice(obs, unlist(lepidoptera_in_prot_areas)) 3.3 # Species data.frames species_1 <- tibble( species = c("Balanus tintinnabulum", "Callinectes sapidus", "Mnemiopsis leidyi"), is_marine = c(TRUE, TRUE, TRUE) ) species_2 <- tibble( species = c("Leuciscus aspius", "Rhithropanopeus harrisii", "Mnemiopsis leidyi"), genus = c("Leuciscus", "Rhithropanopeus", "Mnemiopsis leidyi") ) species_3 <- tibble( species = c("Rhithropanopeus harrisii","Potamopyrgus antipodarum"), informal_group = c("mollusca", "mollusca") ) reduce(list(species_1, species_2, species_3), full_join, by = "species") ``` ### Tina's solution ```r #### CHALLENGE 3A#### # Load lepidoptera_in_prot_areas lepidoptera_in_prot_areas <- readRDS("./data/20240627/20240627_lepidoptera_in_prot_areas.RData") lepidoptera_in_prot_areas #list with the (row numbers of the) lepidoptera observations taken in protected areas. # It's a kind of "special" list, but still a list! So, you can apply all purrr # functions to it. class(lepidoptera_in_prot_areas) # 3.1 how many observations for each protected area? map_int(lepidoptera_in_prot_areas, length) # 3.2 Given obs, the data.frame with the GBIF observations #(see the provided code), how to get a data.frame with only the observations #taken in protected areas? obs <- readr::read_tsv("./data/20240627/20240627_lepidoptera_2024.tsv", na = "") str(obs) protected <- map_df(lepidoptera_in_prot_areas, ~slice(obs,.x)) slice(iris,c(1,3,6)) # 3.3 purrr is more than the map family. To join the data.frames species_1, #species_2 and species_3 (code provided) you could apply full_join() twice: # Species data.frames species_1 <- tibble( species = c("Balanus tintinnabulum", "Callinectes sapidus", "Mnemiopsis leidyi"), is_marine = c(TRUE, TRUE, TRUE) ) species_2 <- tibble( species = c("Leuciscus aspius", "Rhithropanopeus harrisii", "Mnemiopsis leidyi"), genus = c("Leuciscus", "Rhithropanopeus", "Mnemiopsis leidyi") ) species_3 <- tibble( species = c("Rhithropanopeus harrisii","Potamopyrgus antipodarum"), informal_group = c("mollusca", "mollusca") ) full_join(species_1, species_2, by = "species") %>% full_join(species_3, by = "species") reduce(list(species_1,species_2,species_3),full_join) #### Challenge 3B#### # Height of 5 giraffes height_giraffe <- runif(n = 6, min = 4.3, max = 5.7) # Weight of 5 giraffes weight_giraffe <- rnorm(n = 6, mean = 1192, sd = 300) # GPS tracker ID gps_tracker_giraffe <- c("A31", "E4T", "RT4", "YU7", "3G1", "ON9") # Data.frame with weight, height and GPS tracker IDs. df <- tibble::tibble( "weight" = weight_giraffe, "height" = height_giraffe, "gpsID" = gps_tracker_giraffe ) # 3B.1 FOR EACH column OF df and class numeric, APPLY FUNCTION floor(). #Modify the columns. df <- df %>% mutate(across(where(is.numeric), ~ map_dbl(., ~ floor(.)))) # 3B.2 FOR EACH column OF df ending with ight (height, weight), #APPLY FUNCTION floor(). Modify the columns. Same result of exercise 1 expected. df <- df %>% mutate(across(ends_with("ight"), ~ floor(.))) # 3B.3 Instead of modifying the columns, how to add new ones? df <- df %>% mutate(across(ends_with("ight"), list(floored=~ floor(.)))) ``` ### Pieter's solution ```r # 3.1 map_vec(lepidoptera_in_prot_areas, length) # 3.2 obs <- readr::read_tsv("./data/20240627/20240627_lepidoptera_2024.tsv", na = "") str(obs) glimpse(obs) map(lepidoptera_in_prot_areas, ~ slice(obs, .x)) # 3.3 # Species data.frames species_1 <- tibble( species = c("Balanus tintinnabulum", "Callinectes sapidus", "Mnemiopsis leidyi"), is_marine = c(TRUE, TRUE, TRUE) ) species_2 <- tibble( species = c("Leuciscus aspius", "Rhithropanopeus harrisii", "Mnemiopsis leidyi"), genus = c("Leuciscus", "Rhithropanopeus", "Mnemiopsis leidyi") ) species_3 <- tibble( species = c("Rhithropanopeus harrisii","Potamopyrgus antipodarum"), informal_group = c("mollusca", "mollusca") ) # Not DRY solution (hardcoded) full_join(species_1, species_2, by = "species") %>% full_join(species_3, by = "species") reduce( list( species_1, species_2, species_3 ), ~ full_join(.x , .y, by = join_by(species)) ) ## Challenge 3B # Height of 5 giraffes height_giraffe <- runif(n = 6, min = 4.3, max = 5.7) # Weight of 5 giraffes weight_giraffe <- rnorm(n = 6, mean = 1192, sd = 300) # GPS tracker ID gps_tracker_giraffe <- c("A31", "E4T", "RT4", "YU7", "3G1", "ON9") # Data.frame with weight, height and GPS tracker IDs. df <- tibble::tibble( "weight" = weight_giraffe, "height" = height_giraffe, "gpsID" = gps_tracker_giraffe ) # 3B.1 mutate(df, across(where(is.numeric), floor)) # 3B.2 mutate(df, across(ends_with('ight'), floor)) # 3B.3 summarise(df, .by = where(is.numeric), across(everything())) ``` ## Bonus Challange ### Pieter's solution ```r # BC.1 penguin_plot_tbl <- penguins %>% group_by(species) %>% nest() %>% mutate(plot = map(data, ~ ggplot(.x) + geom_point(aes( x = bill_length_mm, y = bill_depth_mm, colour = sex ) ) + ggtitle(species) ) ) # BC.2 paths <- paste0(penguin_plot_tbl$species, ".png") plots <- pull(penguin_plot_tbl, plot) pwalk(list(paths, plots), ggsave, path = "./data/20240627") ` ```