diff --git a/R/tests/testthat/run_all_testthat_tests.Rmd b/R/tests/testthat/run_all_testthat_tests.Rmd index 29ead9e..bdc6781 100755 --- a/R/tests/testthat/run_all_testthat_tests.Rmd +++ b/R/tests/testthat/run_all_testthat_tests.Rmd @@ -14,7 +14,7 @@ rm(list = ls()) if (!require(testthat)) install.packages('testthat') library(testthat) -testing_path <- "/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/tests/testthat/" +testing_path <- "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/tests/testthat/" # Run the testthat tests for each function diff --git a/R/tests/testthat/test_combine_raw_data.R b/R/tests/testthat/test_combine_raw_data.R index 4002490..673cdca 100644 --- a/R/tests/testthat/test_combine_raw_data.R +++ b/R/tests/testthat/test_combine_raw_data.R @@ -6,9 +6,9 @@ rm(list = ls()) if (!require(testthat)) install.packages('testthat') library(testthat) -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/combine_raw_data.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/utilities.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") # This testing file can be run by calling test_file("./path/to/this/file) diff --git a/R/tests/testthat/test_detect_clusters.R b/R/tests/testthat/test_detect_clusters.R index b6fbbb0..b030179 100755 --- a/R/tests/testthat/test_detect_clusters.R +++ b/R/tests/testthat/test_detect_clusters.R @@ -6,9 +6,9 @@ rm(list = ls()) if (!require(testthat)) install.packages('testthat') library(testthat) -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/detect_clusters.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/utilities.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") # This testing file can be run by calling test_file("./path/to/this/file) diff --git a/R/tests/testthat/test_detect_perching_events.R b/R/tests/testthat/test_detect_perching_events.R index 0ccb1da..7928877 100644 --- a/R/tests/testthat/test_detect_perching_events.R +++ b/R/tests/testthat/test_detect_perching_events.R @@ -6,9 +6,9 @@ rm(list = ls()) if (!require(testthat)) install.packages('testthat') library(testthat) -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/detect_perching_events.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/utilities.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") # This testing file can be run by calling test_file("./path/to/this/file) diff --git a/R/tests/testthat/test_preprocess_detections.R b/R/tests/testthat/test_preprocess_detections.R index 736f523..e92f214 100755 --- a/R/tests/testthat/test_preprocess_detections.R +++ b/R/tests/testthat/test_preprocess_detections.R @@ -6,9 +6,9 @@ rm(list = ls()) if (!require(testthat)) install.packages('testthat') library(testthat) -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/preprocess_detections.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/utilities.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") # This testing file can be run by calling test_file("./path/to/this/file) @@ -25,9 +25,9 @@ test_that("The correct number and timing of discrete movement events are retaine withr::local_package("pbapply") # Just for code development - library(tidyverse) - library(lubridate) - library(testthat) + # library(tidyverse) + # library(lubridate) + # library(testthat) # Create a temporary directory for testing. Files will be written and read here path <- "/home/gsvidaurre/Desktop" @@ -144,6 +144,139 @@ test_that("The correct number and timing of discrete movement events are retaine }) +test_that("The correct number and timing of discrete movement events are retained per pre-processing mode for RFID data and a single temporal threshold for an even number of timestamps", { + + # Avoid library calls and other changes to the virtual environment + # See https://r-pkgs.org/testing-design.html + withr::local_package("tidyverse") + withr::local_package("plyr") + withr::local_package("dplyr") + withr::local_package("lubridate") + withr::local_package("pbapply") + + # Just for code development + # library(tidyverse) + # library(lubridate) + # library(testthat) + + # Create a temporary directory for testing. Files will be written and read here + path <- "/home/gsvidaurre/Desktop" + data_dir <- "tmp_tests" + tmp_path <- file.path(path, data_dir) + + if(!dir.exists(tmp_path)){ + dir.create(tmp_path) + } + + # Create the input data directory that the function expects + if(!dir.exists(file.path(tmp_path, "raw_combined"))){ + dir.create(file.path(tmp_path, "raw_combined")) + } + + # Generate a file with pre-processed timestamps for one sensor + + # Create 4 clusters of detections: each cluster consists of 20 detections spaced 0.5 seconds apart + starts <- as.POSIXct(c( + "2023-01-01 01:00:00 EST", + "2023-01-01 02:00:00 EST", + "2023-01-01 01:05:00 EST", + "2023-01-01 02:05:00 EST" + )) + + ends <- starts + 10 + + event_ts <- sapply(1:length(starts), function(x){ + + tmp <- seq(starts[x], ends[x], 0.5) + tmp <- tmp[-length(tmp)] + + return(tmp) + + }, simplify = FALSE) + + # Write out a spreadsheet with these timestamps that will be used as input data for the function + sim_ts <- data.frame(timestamp_ms = c(event_ts[[1]], event_ts[[2]], event_ts[[3]], event_ts[[4]])) %>% + dplyr::mutate( + chamber_id = "Box_01", + year = year(timestamp_ms), + month = month(timestamp_ms), + day = day(timestamp_ms), + original_timestamp = gsub(" EST", "" , gsub("2023-01-01 ", "", timestamp_ms)), + sensor_id = "RFID", + data_type = "RFID", + data_stage = "raw_combined", + date_combined = Sys.Date(), + PIT_tag_ID = "test" + ) + + write.csv(sim_ts, file.path(tmp_path, "raw_combined", "combined_raw_data_RFID.csv"), row.names = FALSE) + + th <- 1 + + ####### `retain_first` mode ####### + preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = th, mode = "retain_first", pixel_threshold = NULL, drop_tag = NULL, path = path, data_dir = file.path(data_dir, "raw_combined"), out_dir = file.path(data_dir, "processed"), tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + + # Read in the output, check the output, then delete all files + test_res <- read.csv(file.path(tmp_path, "processed", "pre_processed_data_RFID.csv")) %>% + # Make sure the timestamps are in the right format + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) + + # Check that the results contain the expected number of detection clusters + expect_equal(nrow(test_res), length(starts)) + + # Check that each detection cluster is separated by more than the given temporal threshold + diffs <- test_res$timestamp_ms - lag(test_res$timestamp_ms) + diffs <- diffs[!is.na(diffs)] + + expect_true(all(diffs >= th)) + + # Check that the first timestamp from the raw data is returned as the timestamp per detection cluster + tmp_starts <- starts[order(starts)] + + invisible(lapply(1:nrow(test_res), function(x){ + + expect_equal(test_res$timestamp_ms[x], tmp_starts[x]) + + })) + + ####### `thin` mode ####### + preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag_ID", pixel_col_nm = NULL, thin_threshold = th, mode = "thin", pixel_threshold = NULL, drop_tag = NULL, path = path, data_dir = file.path(data_dir, "raw_combined"), out_dir = file.path(data_dir, "processed"), tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + + # Read in the output, check the output, then delete all files + test_res <- read.csv(file.path(tmp_path, "processed", "pre_processed_data_RFID.csv")) %>% + # Make sure the timestamps are in the right format + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) + + # Check that the results contain the expected number of detections. This should be the number of total clusters by the number of detections expected per cluster using mode = 'thin' + expect_equal(nrow(test_res), length(starts) * length(seq(1, length(event_ts[[1]]), 2))) + + # Test detections are separated by more than the given temporal threshold + diffs <- test_res$timestamp_ms - lag(test_res$timestamp_ms) + diffs <- diffs[!is.na(diffs)] + + expect_true(all(diffs >= th)) + + # Test that the every other timestamp from the raw data is returned as the timestamp per detection cluster + tstmps <- c( + event_ts[[1]][-seq(2, length(event_ts[[1]]), 2)], + event_ts[[3]][-seq(2, length(event_ts[[3]]), 2)], + event_ts[[2]][-seq(2, length(event_ts[[2]]), 2)], + event_ts[[4]][-seq(2, length(event_ts[[4]]), 2)] + ) + + expect_equal(test_res$timestamp_ms, tstmps) + + # Remove the temporary directory and all files within it + if(tmp_path == file.path(path, data_dir)){ + unlink(tmp_path, recursive = TRUE) + } + +}) + test_that("The correct number and timing of discrete movement events are retained per pre-processing mode for RFID data and a single temporal threshold when movement events are short", { # Avoid library calls and other changes to the virtual environment diff --git a/R/tests/testthat/test_score_clusters.R b/R/tests/testthat/test_score_clusters.R index c433c43..41008d4 100644 --- a/R/tests/testthat/test_score_clusters.R +++ b/R/tests/testthat/test_score_clusters.R @@ -6,9 +6,9 @@ rm(list = ls()) if (!require(testthat)) install.packages('testthat') library(testthat) -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/score_clusters.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R") -source("/home/gsvidaurre/Desktop/GitHub_repos/Abissmal/R/utilities.R") +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") # This testing file can be run by calling test_file("./path/to/this/file) diff --git a/R/vignettes/.Rhistory b/R/vignettes/.Rhistory new file mode 100644 index 0000000..11ecdcc --- /dev/null +++ b/R/vignettes/.Rhistory @@ -0,0 +1,512 @@ +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# Add an x-axis title +xlab("Time of day (HH:MM)") + +# Remove the y-axis grid lines (major and minor) inside of each panel +theme( +panel.grid.major.y = element_blank(), +panel.grid.minor.y = element_blank() +) +gg +gg <- gg + +# Add the perching events as rounded segments, and now encode color through the column individual_initiated +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(!is.na(perching_sensor)), +aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), +linewidth = 2, lineend = "round" +) + +# Add the custom colors +scale_color_manual(values = cols) +gg +gg <- gg + +# Increase the legend text size and reduce white space between the plot and legend +theme( +legend.text = element_text(size = 10), +legend.margin = margin(-1, -1, -1, -1, unit = "pt") +) + +# Change the legend titles +guides( +linetype = guide_legend(title = "Direction"), +color = guide_legend(title = "Individual") +) +gg +gg +# Save the image file to your computer +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "1357aabbcc"), +aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), +color = "orange", +linewidth = 0.5 +) + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "unassigned"), +aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), +color = "black", +linewidth = 0.5 +) + +# Add the perching events as rounded segments, and now encode color through the column individual_initiated +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(!is.na(perching_sensor)), +aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), +linewidth = 2, lineend = "round" +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Add the custom colors +scale_color_manual(values = cols) + +# Change the legend titles +guides( +linetype = guide_legend(title = "Direction"), +color = guide_legend(title = "Individual") +) + +# Add an x-axis title +xlab("Time of day (HH:MM)") + +# Remove the y-axis label +ylab("") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +# Use this function to convert the plot background to black and white +theme_bw() + +# Use aesthetics functions to remove the y-axis labels and ticks +# Add an argument to change where the legend is located in the plot +# Remove the y-axis grid lines (major and minor) inside of each panel +# Increase the legend text size and reduce white space between the plot and legend +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top", +panel.grid.major.y = element_blank(), +panel.grid.minor.y = element_blank(), +legend.text = element_text(size = 10), +legend.margin = margin(-1, -1, -1, -1, unit = "pt") +) +dev.off() +ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "1357aabbcc"), +aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), +color = "orange", +linewidth = 0.5 +) + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "unassigned"), +aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), +color = "black", +linewidth = 0.5 +) + +# Add the perching events as rounded segments, and now encode color through the column individual_initiated +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(!is.na(perching_sensor)), +aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), +linewidth = 2, lineend = "round" +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Add the custom colors +scale_color_manual(values = cols) + +# Change the legend titles +guides( +linetype = guide_legend(title = "Direction"), +color = guide_legend(title = "Individual") +) + +# Add an x-axis title +xlab("Time of day (HH:MM)") + +# Remove the y-axis label +ylab("") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +# Use this function to convert the plot background to black and white +theme_bw() + +# Use aesthetics functions to remove the y-axis labels and ticks +# Add an argument to change where the legend is located in the plot +# Remove the y-axis grid lines (major and minor) inside of each panel +# Increase the legend text size and reduce white space between the plot and legend +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top", +panel.grid.major.y = element_blank(), +panel.grid.minor.y = element_blank(), +legend.text = element_text(size = 10), +legend.margin = margin(-1, -1, -1, -1, unit = "pt") +) +library(tidyverse +) +library(tidyverse +?is.na +is.na(x) +x <- c(1, NA, 2, 3, NA) +is.na(x) +!is.na(x) +?group_by() +group_by() +library(tidyverse) +group_by() +library(tidyverse +) +# Create a vector with character, numeric, and binary data types +# Since you're not saving the output into an object, the result will print directly to the console +c("1", 1, TRUE, FALSE) +# Create a vector with numeric and binary data types +c(1, 1, TRUE, FALSE) +# Create a vector with numeric and binary data types +c(1, 1, TRUE, FALSE) +# You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts +5:length(rfid_ts) +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") +# See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements +glimpse(rfid_ts) +# Create a vector of 4 RFID timestamps or 4 elements in HH:MM:SS format +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") +# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit +o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") +# See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements +glimpse(rfid_ts) +# Simulate some RFID detection failures across both beam breaker pairs +# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) +# Simulate some stray detections for the outer beam breaker +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") +glimpse(o_irbb_ts) +# You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts +5:length(rfid_ts) +# You can also use the function `seq()` to create the same sequence of numeric indices +seq(from = 5, to = length(rfid_ts), by = 1) +# If you want to filter out non-consecutive elements, you can create a vector of indices with the function `c()` +c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14) +# When you put any of the expressions above inside of square brackets after the object name you will pull out elements 5 to the length of rfid_ts and drop the first 4 elements +rfid_ts[5:length(rfid_ts)] +rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] +rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] +# Finally, you can use any of the methods above to create a sequence of indices that you want to drop, and then use the `-` symbol inside of the square brackets to remove those indices. For instance: +rfid_ts[-c(1:4)] # the numbers must be wrapped in `c()` in order for this inverted filtering to work +# This statement should yield TRUE, because these vectors are not the same length +length(rfid_ts[-c(1:4)]) != length(exp_rep) +# Create a vector with information about the experimental replicate +# The argument `x` contains the metadata information that will be repeated +# The argument `times` specifies the number of times that the information will be repeated +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) +glimpse(exp_rep) +# This statement should yield TRUE, because these vectors are not the same length +length(rfid_ts[-c(1:4)]) != length(exp_rep) +sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) +sim_dats <- data.frame(exp_rep, rfid_ts) +glimpse(sim_dats) +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +glimpse(sim_dats) +sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts))) +glimpse(sim_dats) +# This function returns a vector of the column names of the data frame +names(sim_dats) +# Use square bracket indexing and the function `ncol()` to find the last column name +ncol(sim_dats) # 3 columns in this data frame +# This expression gets you the name of the last column +names(sim_dats)[ncol(sim_dats)] +# Then you can overwrite the last column name with a new name +names(sim_dats)[ncol(sim_dats)] <- "year" +# Confirm that the name was changed correctly +names(sim_dats) +glimpse(sim_dats) +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) +# Use the tidyverse to add the year as the 3rd column +sim_dats %>% +glimpse() %>% # See the structure of sim_dats +dplyr::mutate( +# The nrow(.) expression means "get the number of rows for the current object", which in this case is sim_dats +year = rep(2023, nrow(.)) +) %>% +glimpse() # See the structure of sim_dats with the new column year +# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console +sim_dats_rfid$day +# Load the function that combines raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") +# Load the function that detects perching events in the raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") +# Load the function that pre-processes raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") +# Load a script with utility functions that each function above requires +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") +View(preprocess_detections) +rm(list = ls()) # Clean global environment +library(tidyverse) # Load the set of tidyverse packages +library(data.table) # Load other packages that the ABISSMAL functions require +# Initialize an object with the path that is your working directory +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" +# Load the function that detects clusters in the pre-processed data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") +# Load the function that scores behavioral inferences about clusters +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R") +# Load a script with utility functions that each function above requires +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") +scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% +# The timestamps must be converted to POSIX format every time that the data is read back into R for plotting +dplyr::mutate( +start = as.POSIXct(format(as.POSIXct(start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), +end = as.POSIXct(format(as.POSIXct(end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) +) %>% +# Arrange the rows by increasing timestamps +dplyr::arrange(-desc(start)) +glimpse(scored_clusters) +scored_clusters %>% +# Extract the day from each timestamp and make a new column with this information +dplyr::mutate( +day = lubridate::day(start) +) %>% +# Here you're using the function is.na(), which will return TRUE when it finds an NA (missing) value in the given column. By placing the ! before is.na(), you're inverting the output, so that all TRUE values are converted to FALSE. As a result, dplyr::filter will drop all rows that return the value FALSE (e.g. all rows with missing values in the column direction_scored) +dplyr::filter(!is.na(direction_scored)) %>% +# Group the data frame by both columns for which you want to count rows +group_by(day, direction_scored) %>% +# Then summarize the data: the number of rows here is the number of exits or entrances scored per day +dplyr::summarise( +n = n() +) +# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA +is.na(scored_clusters$individual_initiated) +# All NAs in this column are converted to "unassigned", but all other values were not changed +ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) +# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA +is.na(scored_clusters$individual_initiated) +# All NAs in this column are converted to "unassigned", but all other values were not changed +ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) +scored_clusters_gg <- scored_clusters %>% +dplyr::mutate( +# If this column has an NA value, then convert it to "unassigned" +# Else, do not change the given value +individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated), +# Repeat this process for the direction_scored column but with a different value +# Also, in the conditional statement below, you added is.na(perching_sensor) to only convert values of direction_scored when they were not labeled as perching events +direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored) +) %>% +# Next, you'll convert each of these columns to type factor and arrange the levels in order for plotting +dplyr::mutate( +individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")), +direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored")) +) +# Check the resulting changes using the function distinct() to see all of the unique values for both columns that were modified +# The NA values in the direction_scored column are expected since these refer to perching events +scored_clusters_gg %>% +distinct(individual_initiated, direction_scored) +# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA +is.na(scored_clusters$individual_initiated) +# All NAs in this column are converted to "unassigned", but all other values were not changed +ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) +scored_clusters_gg <- scored_clusters %>% +dplyr::mutate( +# If this column has an NA value, then convert it to "unassigned" +# Else, do not change the given value +individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated), +# Repeat this process for the direction_scored column but with a different value +# Also, in the conditional statement below, you added is.na(perching_sensor) to only convert values of direction_scored when they were not labeled as perching events +direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored) +) %>% +# Next, you'll convert each of these columns to type factor and arrange the levels in order for plotting +dplyr::mutate( +individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")), +direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored")) +) +# Check the resulting changes using the function distinct() to see all of the unique values for both columns that were modified +# The NA values in the direction_scored column are expected since these refer to perching events +scored_clusters_gg %>% +distinct(individual_initiated, direction_scored) +# Colors will be encoded in the same order as the levels of the column individual_initiated, so orange will encode "1357aabbcc" +levels(scored_clusters_gg$individual_initiated) +cols <- c("orange", "darkgreen", "black") +# Line types will be encoded in the same order as the levels of the column direction_scored, so dotted will encode "not scored" +levels(scored_clusters_gg$direction_scored) +ltys <- c("solid", "longdash", "dotted") +ggplot() + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg %>% +dplyr::filter(individual_initiated == "1357aabbcc"), +aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), +color = "orange", +linewidth = 0.5 +) + +# Add a vertical line for each non-perching event that was not assigned to either individual +geom_segment( +data = scored_clusters_gg %>% +dplyr::filter(individual_initiated == "unassigned"), +aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), +color = "black", +linewidth = 0.5 +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Use this function to convert the plot background to black and white +theme_bw() + +# Use aesthetics functions to remove the y-axis labels and ticks +# Add an argument to change where the legend is located in the plot +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top" +) +# Create a new column in the raw data for the date of data collection +scored_clusters_gg2 <- scored_clusters_gg %>% +# First you need to create a column with information about the day +dplyr::mutate( +day = lubridate::day(start) +) %>% +dplyr::mutate( +# Then recode the label for each day and save this in a new column +day_label = ifelse(day == 1, "Day 1", day), # Here the last argument is day because the column day_label does not exist yet +day_label = ifelse(day == 2, "Day 2", day_label), +day_label = ifelse(day == 3, "Day 3", day_label) +) +# Looks good +glimpse(scored_clusters_gg2) +scored_clusters_gg2 %>% +distinct(day_label) +# Add the data frame as the default dataset for the base layer plot, so that the facet_wrap() layer below has data to plot +ggplot(data = scored_clusters_gg2) + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg2 %>% +dplyr::filter(individual_initiated == "1357aabbcc"), +aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), +color = "orange", +linewidth = 0.5 +) + +# Add a vertical line for each non-perching event that was not assigned to either individual +geom_segment( +data = scored_clusters_gg2 %>% +dplyr::filter(individual_initiated == "unassigned"), +aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), +color = "black", +linewidth = 0.5 +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Use this function to convert the plot background to black and white +theme_bw() + +# Use aesthetics functions to remove the y-axis labels and ticks +# Add an argument to change where the legend is located in the plot +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top" +) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 3, strip.position = "left") +scored_clusters_gg3 <- scored_clusters_gg2 %>% +dplyr::mutate( +start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")), +end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S")) +) +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(scored_clusters_gg3) +gg <- ggplot(data = scored_clusters_gg3) + +# Add a vertical line for each non-perching event assigned to the first individual +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "1357aabbcc"), +aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), +color = "orange", +linewidth = 0.5 +) + +# Add a vertical line for each non-perching event that was not assigned to either individual +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(individual_initiated == "unassigned"), +aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), +color = "black", +linewidth = 0.5 +) + +# Add the custom linetype values to this plot +scale_linetype_manual(values = ltys) + +# Remove the y-axis label +ylab("") + +# Use this function to convert the plot background to black and white +theme_bw() + +# Use aesthetics functions to remove the y-axis labels and ticks +# Add an argument to change where the legend is located in the plot +theme( +axis.text.y = element_blank(), +axis.ticks.y = element_blank(), +legend.position = "top" +) + +# Facet the plot by day (e.g. create a panel per day) +# Use the new day labels here +facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +# Change the aesthetics of the x-axis labels +scale_x_datetime( +date_breaks = "30 mins", +date_labels = "%H:%M" +) + +# Add an x-axis title +xlab("Time of day (HH:MM)") + +# Remove the y-axis grid lines (major and minor) inside of each panel +theme( +panel.grid.major.y = element_blank(), +panel.grid.minor.y = element_blank() +) +gg +gg <- gg + +# Add the perching events as rounded segments, and now encode color through the column individual_initiated +geom_segment( +data = scored_clusters_gg3 %>% +dplyr::filter(!is.na(perching_sensor)), +aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), +linewidth = 2, lineend = "round" +) + +# Add the custom colors +scale_color_manual(values = cols) +gg +gg <- gg + +# Increase the legend text size and reduce white space between the plot and legend +theme( +legend.text = element_text(size = 10), +legend.margin = margin(-1, -1, -1, -1, unit = "pt") +) + +# Change the legend titles +guides( +linetype = guide_legend(title = "Direction"), +color = guide_legend(title = "Individual") +) +gg +gg +# Save the image file to your computer +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) diff --git a/R/vignettes/README.md b/R/vignettes/README.md new file mode 100644 index 0000000..9f02dc7 --- /dev/null +++ b/R/vignettes/README.md @@ -0,0 +1,45 @@ +

ABISSMAL R vignettes

+Developer:
+Grace Smith-Vidaurre, PhD: gsvidaurre[at]gmail.com +
+ +

Overview

+ +The goal of these vignettes is to disseminate R coding skills in a biological context through the ABISSMAL behavioral tracking system. There are six vignettes that cover the following topics: + +1. An introduction to RStudio that includes downloading the ABISSMAL GitHub repository, an introduction to the ABISSMAL data processing and analysis pipeline, and information about troubleshooting errors and reporting bugs + +2. Learn how to set up a virtual workspace, including cleaning your global environment, running code inside RMarkdown files, package installation, keyboard shortcuts, creating and specifying a working directory + +3. Create simulated datasets of animal movements while learning how to create objects in R, different data types in R, and how to manipulate objects with conditional statements and piping expressions + +4. Save the simulated datasets and learn how to write data as files on your computer, how to manipulate data frames with base R and the tidyverse, and how to write simple and complex loops + +5. Work through the ABISSMAL data processing pipeline while learning how to access and use custom functions, as well as how to plot timestamps in a barcode figure + +6. Finish the ABISSMAL data analysis pipeline while using the R coding skills that you practiced in the earlier vignettes, and end by making a more complex and refined barcode figure + +Each vignette is available as a RMarkdown (extension `.Rmd`) file that can be opened in RStudio. The knitted output per vignette is available in HTML format, and you can open these files in your default Internet browser. The HTML files contain text, code, and the output of code chunks. + +After completing the vignettes, it would be very helpful if you could complete the brief [Google form](https://forms.gle/98G8aQV8dDMcZz3T7) for a post-vignette evaluation that will help us continue to improve these vignettes over time. + + +

Resumen

+ +Nuestro objetivo es diseminar habilidades de programación en R en un contexto biológico a través del sistema de ABISSMAL. Tenemos seis tutoriales que cubren los siguientes puntos: + +1. Una introducción a RStudio que incluye cómo descargar el repositorio de GitHub de ABISSMAL, una introducción al `pipeline` para procesar y analizar datos de ABISSMAL, e información sobre cómo manejar y reportar errores + +2. Aprender cómo configurar un espacio virtual, incluyendo limpiar tu ambiente global, ejecutar código dentro de piezas de código de archivos de RMarkdown, instalar paquetes, atajos para escribir código, y crear y especificar un directorio de trabajo + +3. Crear datos simulados de movimientos de animales mientras se aprende cómo crear objetos en R, qué son los diferentes tipos de datos en R, y cómo manipular objetos con frases condicionales y expresiones de `piping` + +4. Guardar los datos simulados y aprender cómo escribir datos a archivos físicos en tu computadora, manipular `dataframes` con R base y el `tidyverse`, y escribir bucles (`loops`) sencillos y complejos + +5. Trabajar en el `pipeline` de procesar datos con ABISSMAL mientras aprendes cómo acceder y usar funciones customizadas, y también cómo visualizar marcas de tiempo en una gráfica de barras + +6. Terminar el `pipeline` de análisis de datos de ABISSMAL mientras se emplean las habilidades de programación que practicaste en los tutoriales anteriores, y al final vas a crear una figura de código de barras más compleja y refinada + +Cada tutorial está disponible como un archivo de RMarkdown (extension `.Rmd`) que puedes abrir en RStudio. El reporte de cada tutorial está disponible como un archivo en formato HTML, estos archivos se pueden abrir en cualquier navegador de internet (Google Chrome, Firefox, Safari, etc). Los archivos de HTML contienen texto, código, y los resultados de las piezas de código. + +Sería una gran ayuda si después de completar los tutoriales pudieras completar una encuesta a través de [Google Forms](https://forms.gle/CaQXVWDrY5oHg8mCA). La información que compartas nos ayudará mejorar los tutoriales en el futuro. diff --git a/R/vignettes/Tutorial_01_Introduccion.Rmd b/R/vignettes/Tutorial_01_Introduccion.Rmd new file mode 100644 index 0000000..bb4eca6 --- /dev/null +++ b/R/vignettes/Tutorial_01_Introduccion.Rmd @@ -0,0 +1,119 @@ +--- +title: "Tutorial 01: Introducción" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en este primer tutorial para reportar un "Issue". + +

Resumen del tutorial y objetivos de aprendizaje

+ +En este primer tutorial aprenderás acerca del programa RStudio, cómo crear una versión local del repositorio de ABISSMAL en GitHub, y en qué consiste el `pipeline` de análisis de datos en ABISSMAL. En resumen: + +1. Configurar tu sesión de RStudio +2. Crear una versión local de un repositorio de GitHub +3. Usar ABISSMAL para procesar y analizar datos +4. Solucionar problemas con tu código usando recursos en línea +5. Reportar problemas de código a GitHub + +Hay muchas formas de completar una tarea o solucionar un problema en R, mantén esto en mente mientras completas estos tutoriales. En cada tutorial, vas a aprender diferentes ejemplos sobre cómo manejar ciertas asignaciones o solucionar problemas específicas con código, pero estos ejemplos no son un resumen exhaustivo de cómo manejar cada tarea ni cómo solucionar cada problema individualmente. La recomendación es que uses estos tutoriales como una oportunidad para practicar tus habilidades de escribir código en un contexto biológico. + +

Configurar tu sesión de RStudio

+ +Si nunca has usado R, un lenguaje de programación para análisis estadísticos, ni RStudio, una interfaz gráfica de usuario, recomiendo revisar el tutorial [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder-es/) de [Software Carpentry](https://software-carpentry.org/). + +Cuando hayas instalado R y RStudio en tu computadora, puedes hacer clic en el ícono de RStudio para abrir el programa. La configuración predeterminada de los paneles de RStudio se debería de ver algo así, aunque el color de fondo puede ser diferente: + +
+![Una imagen de la configuración predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) +Con esta configuración de paneles puede ser difícil ver el código escrito y guardado en archivos en el panel de la fuente, así como el resultado de ese código en el panel de la consola. Puedes reconfigurar los paneles para que el panel de la fuente esté al lado izquierdo del panel de la consola, y así facilitará ver los resultados de tu código inmediatamente. Puedes seguir los siguientes pasos para reconfigurar los paneles en RStudio: + +* Selecciona la opción "Tools" en el menú + +* Selecciona la opción "Global Options" en el menú desplegable que sale en "Tools" + +* Se va a abrir otra ventana con opciones. Selecciona la opción "Pane Layout" a la izquierda + +* Usa el menú desplegable para seleccionar "Source" como el panel en la primera fila y al lado izquierdo, y selecciona "Console" como el panel que ocupará la siguiente parte de la primera fila de paneles a tu mano derecha + +* Puedes seleccionar "Apply" para aplicar estos cambios y luego "Ok" para salir de esta ventana + +Tu configuración de paneles en RStudio ahora se debería de ver así: + +
+![Una imagen de la configuración modificada de los paneles de RStudio, con el panel de la consola al lado derecho y el panel de la fuente al lado izquierdo](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) + +Otro cambio que puedes implementar en tu configuración de RStudio es el retorno automático, para no tener que hacer scroll a tu derecha cada vez que quieres leer una línea completa de texto o código. Para implementar este cambio, puedes ir a "Tools", luego "Global Options", seleccionar "Code", y seleccionar la caja para la opción "Soft-wrap R source files", luego hacer clic en "Apply" y "Ok". + +También puedes cambiar el tamaño de la fuente del texto y código que escribes, tanto como el color de texto y código, y el color de fondo de tu ventana de RStudio. Después de seleccionar "Tools" y "Global Options", puedes seleccionar "Appearance" para ver diferentes opciones. + +

Crear una versión local de un repositorio de GitHub

+ +Si nunca has usado GitHub, recomiendo bajar el programa [GitHub Desktop](https://docs.github.com/es/desktop). Este programa es una interfaz gráfica de usuario para la plataforma de GitHub, que provee un sistema de control de versiones. Vamos a seguir los siguientes pasos para crear una versión local del repositorio de ABISSMAL que está en GitHub para que puedas acceder las funciones de R de ABISSMAL en tu propia computadora. Si ya sabes cómo usar GitHub y cómo usar Git en el terminal, puedes seguir las instrucciones que tenemos en el [README de ABISSMAL](https://github.com/lastralab/ABISSMAL). + +Después de instalar GitHub Desktop: + +* Haz clic en el ícono de GitHub Desktop para abrir el programa + +* Abre tu navegador web y ve al repositorio de ABISSMAL en GitHub: https://github.com/lastralab/ABISSMAL + +* Haz clic en el botón verde de "Code" y copia la URL debajo de la opción "HTTPS" en el menú desplegable + +* Ve al menú de GitHub Desktop y selecciona "File" + +* Selecciona "Clone repository" + +* Selecciona la pestaña "URL" + +* Pega la URL de ABISSMAL que copiaste de GitHub en la caja que pide un "Repository URL or GitHub username and repository" + +* Revisa que el directorio en la caja "Local path" es la ubicación en tu computadora donde quieres instalar el repositorio de ABISSMAL. Por ejemplo, si "Local path" es "/home/YourUserName/Desktop/ABISSMAL", el repositorio de ABISSMAL se instalará directamente en tu Desktop + +* Selecciona "Clone" cuando estés lista para crear una versión local del repositorio de ABISSMAL en tu computadora + +Cuando el repositorio se haya instalado en tu computadora, deberías de poder ver el siguiente directorio y una lista de archivos dentro de una carpeta llamada "ABISSMAL": + +
+![Una imagen del directorio de la versión local del repositorio de ABISSMAL](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) + +Los archivos que vamos a usar en los siguientes tutoriales están adentro de la carpeta "R". Esta carpeta contiene 6 archivos de R (extensión ".R"), un archivo README que contiene más información sobre cada archivo de R, y carpetas con los tutoriales en formato RMarkdown (extensión ".Rmd"), y también código para pruebas unitarias automatizadas. + +

Procesar y analizar datos con ABISSMAL

+ +ABISSMAL provee 5 funciones diferentes de R para procesar y analizar datos, y describimos estas funciones en más detalle abajo, en el orden general en que se deberían de usar. Puedes encontrar más información detallada sobre estas funciones en el archivo README de la carpeta "R", y también en el manuscrito asociado con ABISSMAL: + +1. `combine_raw_data` automáticamente combina hojas de cálculo de los datos originales que fueron colectados por día y los guarda en una sola hoja de cálculo a través de todos los días de colección de datos. Esta concatenación de datos se realiza por cada tipo de sensor, por ejemplo, los sensores de rayos infrarrojo o una cámara de infrarrojo, y no cambia ni reemplaza los datos originales. Esta función puede usar datos de los sensores de RFID ("radio frequency identification"), los sensores infrarrojo, la cámara, o el sensor de temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de temperatura + +2. `detect_perching_events` toma como entrada los resultados de `combine_raw_data` y detecta grupos de detecciones que ocurrieron cerca en el tiempo como eventos de posar. Estos eventos de pose representan periodos de tiempo cuando un individuo estuvo postrado en la antena de RFID (como percha) situado en la entrada del contenedor de nido. La función usa datos de los sensores ubicados en la entrada del contenedor (RFID o los sensores de infrarrojo) y devuelve una hoja de cálculo de las coordenadas temporales de los eventos de posar inferidos + +3. `preprocess_detections` usa como entrada los resultados de `combine_raw_data` y remueve detecciones que ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de cálculo, las detecciones consecutivas deberían de estar separadas por uno o más umbrales temporales predeterminados. Por ejemplo, cuando usas un umbral temporal de 1 segundo para filtrar las detecciones originales, solo una detección puede ocurrir por segundo en los datos procesados + +4. `detect_clusters` usa como entrada los resultados de `preprocess_detections` obtenidos con dos o más tipos de sensores. La función identifica detecciones a través de dos o más tipos de sensores que ocurrieron cerca en el tiempo, y devuelve información temporal y metadatos sobre cada grupo o `cluster` (cúmulo). Cada `cluster` de detecciones representa un evento discreto de movimiento de uno o más individuos + +5. `score_clusters` usa como entrada los resultados de `detect_clusters`. Esta función también puede usar los resultados de `detect_perching_events` para integrar los eventos de posar que fueron identificados en los datos originales de RFID o los sensores de infrarrojo, y puede usar los resultados de `preprocess_detections` para integrar los eventos de grabación de vídeos que no fueron incluidos en los `clusters` detectados por `detect_clusters`. Esta función hace inferencias de comportamiento de los eventos de movimiento, incluyendo la dirección de movimiento, la magnitud de movimiento, la identidad del individuo (cuando datos de RFID se encontraron en un `cluster`), y en dónde ocurrió el inicio de la secuencia de movimiento (en la entrada o adentro del contenedor de nido). La función devuelve una hoja de cálculo de inferencias de comportamiento y otros metadatos sobre cada evento de movimiento, y estos resultados se pueden usar para visualizaciones y análisis estadísticos. + +
+![La figura 4 del manuscrito de ABISSMAL con un `pipeline` con las 5 funciones primarias](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) +* Esta figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). + +

Solucionar errores en línea

+ +Mientras escribes y ejecutas código encontrarás errores que a veces pueden ser frustrantes. Experimentar y solucionar los errores que te salen es una parte muy importante de tu proceso de aprendizaje en R (u otros lenguajes de programación). Los errores surgen por diferentes razones, incluyendo errores de mecanografía mientras escribes código, problemas con las versiones de paquetes externos de R que instalaste, o posiblemente por necesitar funciones de paquetes que no has instalado en tu computadora. Los errores a veces pueden surgir por problemas con las funciones de ABISSMAL que usarás en los siguientes tutoriales, o por errores con el código en los tutoriales mismos. + +Cuando experimentas un error con tu código, es importante intentar solucionar el error independientemente antes de suponer que el error nació de errores de las funciones de ABISSMAL o los tutoriales. Hay muchos recursos en línea que puedes usar para solucionar errores comunes o inusuales en R. Puedes empezar con copiar y pegar el mensaje de error que ves en tu consola en un buscador, y así deberías de ver varias opciones de foros públicos dónde otras personas han consultado y solucionado errores similares. También deberías de poder de usar herramientas de IA generativo como ChatGPT para buscar errores de mecanografía u otros problemas con tu código. Otra opción útil es leer la documentación de R para investigar si el error que ves esta relacionado con un paquete o una función que puede ser una dependencia de ABISSMAL. + +Ya cuando hayas investigado cuidadosamente un error, y estas segura de que el error no se debe a un error de mecanografía o problemas con la estructura de tus datos, luego puedes considerar si el error se debe a un problema con las funciones de ABISSMAL o los tutoriales mismos, y puedes reportar el error a las desarolladoras de ABISSMAL en GitHub (ver la siguiente sección). + +

Reportar errores en GitHub

+ +Es posible que encuentres errores mientras trabajas en cada tutorial, incluyendo errores con código que no ejecuta, o código que devuelve resultados incorrectos. Estos errores se pueden manifestar desde las funciones de ABISSMAL o con el código de algún tutorial. Si encuentras un error en una función de ABISSMAL, puedes crear un "Issue" (una tarea o problema) en el repositorio de GitHub. Para crear un "Issue" nuevo, puedes seleccionar "New Issue" en la página de [Issues](https://github.com/lastralab/ABISSMAL/issues) en el repositorio de ABISSMAL, y seguir las instrucciones en el esquema del "Issue" nuevo para añadir la información que las desarrolladoras necesitan para trabajar efectivamente en el error. También puedes añadir la etiqueta de "bug" (o error) a tu "Issue". Si encuentras un error con los tutoriales puedes crear un "Issue" usando los mismos pasos, y deberías de clarificar que el error está relacionado al código de algún tutorial en particular. + +En el siguiente tutorial, vamos a crear datos simulados de movimiento de animales para aprender cómo usar las cinco funciones primarias de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_01_Introduccion.html b/R/vignettes/Tutorial_01_Introduccion.html new file mode 100644 index 0000000..54a1518 --- /dev/null +++ b/R/vignettes/Tutorial_01_Introduccion.html @@ -0,0 +1,1854 @@ + + + + + + + + + + + + + + + +Tutorial 01: Introducción + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, +siguiendo las convenciones +de traducción de los Carpentries, incluyendo usar el género femenino +por defecto. Si encuentras errores de ortografía que impiden tu +habilidad de completar los tutoriales, por favor reporta los errores de +ortografía a GitHub usando los pasos en este primer tutorial para +reportar un “Issue”.

+

+Resumen del tutorial y objetivos de aprendizaje +

+

En este primer tutorial, vas a leer sobre el programa RStudio, +aprender cómo crear una versión local de el repositorio de ABISSMAL en +GitHub, y aprender más sobre el pipeline de análisis de +datos en ABISSMAL. En este tutorial vas a aprender cómo:

+
    +
  1. Configurar tu sesión de RStudio
  2. +
  3. Crear una versión local de un repositorio de GitHub
  4. +
  5. Usar ABISSMAL para procesar y analizar datos
  6. +
  7. Solucionar problemas con tu código usando recursos en línea
  8. +
  9. Reportar problemas de código a GitHub
  10. +
+

Hay muchas formas de hacer una sola tarea o solucionar un problema en +R, y deberías de mantener esto en mente mientras completas estos +tutoriales. En cada tutorial, vas a aprender diferentes ejemplos sobre +cómo manejar ciertas tares o solucionar problemas específicas con +código, pero estos ejemplos no son un resumen exhaustivo de cómo manejar +cada tarea ni cómo solucionar cada problema. Más bien recomiendo que +usas estos tutoriales como una oportunidad para practicar cómo usar tus +habilidades de escribir código en un contexto biológico.

+

+Configurar tu sesión de RStudio +

+

Si nunca has usado R, un lenguaje de programación para análisis +estadísticos, ni RStudio, una interfaz gráfica de usuario, recomiendo +revisar el tutorial R for +Reproducible Scientific Analysis de Software Carpentry.

+

Cuando hayas instalado R y RStudio en tu computadora, puedes hacer +clic en el ícono de RStudio para abrir el programa. La configuración +predeterminada de los paneles de RStudio se debería de ver algo así, +aunque el color de fondo puede ser diferente:

+


Una imagen de la configuración predeterminada de los paneles de RStudio, con el panel de la consola debajo del panel de la fuente +Con esta configuración de paneles es difícil de ver el código que +escribes y guardas en archivos físicos en el panel de la fuente, tanto +como el resultado de ese código en el panel de la consola. Puedes +reconfigurar los paneles para que el panel de la fuente esté al lado +izquierdo del panel de la consola, y así será más fácil de ver los +resultados de tu código inmediatamente. Puedes seguir los siguientes +pasos para reconfigurar los paneles en RStudio:

+
    +
  • Selecciona la opción “Tools” en el menú

  • +
  • Selecciona la opción “Global Options” en el menú desplegable que +sale en “Tools”

  • +
  • Se va a abrir otra ventana con opciones. Selecciona la opción +“Pane Layout” a la izquierda

  • +
  • Usa el menú desplegable para seleccionar “Source” como el panel +en la primera fila y al lado izquierdo, y selecciona “Console” como el +panel que ocupará la siguiente parte de la primera fila de paneles a tu +mano derecha

  • +
  • Puedes seleccionar “Apply” para aplicar estos cambios y luego +“Ok” para salir de esta ventana

  • +
+

Tu configuración de paneles en RStudio ahora se debería de ver +así:

+


Una imagen de la configuración modificada de los paneles de RStudio, con el panel de la consola al lado derecho y el panel de la fuente al lado izquierdo

+

Otro cambio que puedes implementar en tu configuración de RStudio es +el retorno automático, para no tener que hacer scroll a tu derecha cada +vez que quieres leer una línea completa de texto o código. Para +implementar este cambio, puedes ir a “Tools”, luego “Global Options”, +seleccionar “Code”, y seleccionar la caja para la opción “Soft-wrap R +source files”, luego hacer clic en “Apply” y “Ok”.

+

También puedes cambiar el tamaño de la fuente del texto y código que +escribes, tanto como el color de texto y código, y el color de fondo de +tu ventana de RStudio. Después de seleccionar “Tools” y “Global +Options”, puedes seleccionar “Appearance” para ver diferentes +opciones.

+

+Crear una versión local de un repositorio de GitHub +

+

Si nunca has usado GitHub, recomiendo bajar el programa GitHub Desktop. Este +programa es una interfaz gráfica de usuario para el plataforma de +GitHub, que provee un sistema de control de versiones. Vamos a seguir +unos pasos para crear una versión local del repositorio de ABISSMAL que +está en GitHub para que puedas acceder las funciones de R de ABISSMAL en +tu propia computadora. Si ya sabes cómo usar GitHub y cómo usar Git en +el terminal, puedes seguir las instrucciones que tenemos en el README de ABISSMAL.

+

Cuando hayas instalado GitHub Desktop, puedes:

+
    +
  • Hacer clic en el ícono de GitHub Desktop para abrir el +programa

  • +
  • Abrir tu navegador web e ir al repositorio de ABISSMAL en GitHub: +https://github.com/lastralab/ABISSMAL

  • +
  • Hacer clic en el botón verde de “Code” y copiar la URL debajo de +la opción “HTTPS” en el menú desplegable

  • +
  • Ir al menú de GitHub Desktop y seleccionar “File”

  • +
  • Seleccionar “Clone repository”

  • +
  • Seleccionar la pestaña “URL”

  • +
  • Pegar la URL de ABISSMAL que copiaste de GitHub en la caja que +pide un “Repository URL or GitHub username and repository”

  • +
  • Revisar que el directorio en la caja “Local path” es la ubicación +en tu computadora donde quieres instalar el repositorio de ABISSMAL. Por +ejemplo, si “Local path” es “/home/User/Desktop/ABISSMAL”, el +repositorio de ABISSMAL se instalará directamente en tu Desktop

  • +
  • Seleccionar “Clone” cuando estés lista para crear una versión +local del repositorio de ABISSMAL en tu computadora

  • +
+

Cuando el repositorio se haya instalado en tu computadora, deberías +de poder ver el siguiente directorio y una lista de archivos adentro de +una carpeta llamada “ABISSMAL”:

+


Una imagen del directorio de la versión local del repositorio de ABISSMAL

+

Los archivos que vamos a usar en los siguientes tutoriales están +adentro de la carpeta “R”. Esta carpeta contiene 6 archivos de R +(extensión “.R”), un archivo README que contiene más información sobre +cada archivo de R, y carpetas con los tutoriales en formato RMarkdown +(extensión “.Rmd”), y también código para pruebas unitarias +automatizadas.

+

+Procesar y analizar datos con ABISSMAL +

+

ABISSMAL provee 5 funciones diferentes de R para procesar y analizar +datos, y describimos estas funciones en más detalle abajo, en el orden +general en que se deberían de usar. Puedes encontrar más información +detallada sobre estas funciones en el archivo README de la carpeta “R”, +y también en el manuscrito asociado con ABISSMAL:

+
    +
  1. combine_raw_data automáticamente combina hojas de +cálculo de los datos originales que fueron colectados por día y los +guarda en una sola hoja de cálculo a través de todos los días de +colección de datos. Esta concatenación de datos se realiza por cada tipo +de sensor, por ejemplo, los sensores de rayos infrarrojo o una cámara de +infrarrojo, y no cambia ni reemplaza los datos originales. Esta función +puede usar datos de los sensores de RFID (“radio frequency +identification”), los sensores infrarrojo, la cámara, o el sensor de +temperatura de ABISSMAL. Las siguientes funciones no aceptan datos de +temperatura

  2. +
  3. detect_perching_events toma como entrada los +resultados de combine_raw_data y detecta grupos de +detecciones que ocurrieron cerca en el tiempo como eventos de posar. +Estos eventos de posa representan periodos de tiempo cuando un individuo +estuvo posado en la antena de RFID (como percha) situado en la entrada +del contenedor de nido. La función usa datos de los sensores ubicados en +la entrada del contenedor (RFID o los sensores de infrarrojo) y devuelve +una hoja de cálculo de las coordenadas temporales de los eventos de +posar inferidos

  4. +
  5. preprocess_detections usa como entrada los +resultados de combine_raw_data y remueve detecciones que +ocurrieron muy cerca en el tiempo. Esta función devuelve una hoja de +cálculo de datos pre-procesados por tipo de sensor, y en cada hoja de +cálculo, las detecciones consecutivas deberían de estar separadas por un +umbral temporal predeterminado o más. Por ejemplo, cuando usas un umbral +temporal de 1 segundo para filtrar las detecciones originales, solo una +detección puede ocurrir por segundo en los datos procesados

  6. +
  7. detect_clusters usa como entrada los resultados de +preprocess_detections obtenidos con dos o más tipos de +sensores. La función identifica detecciones a través de dos o más tipos +de sensores que ocurrieron cerca en el tiempo, y devuelve información +temporal y metadatos sobre cada grupo o cluster (cúmulo). +Cada cluster de detecciones representa un evento discreto +de movimiento de un individuo o más que un individuo

  8. +
  9. score_clusters usa como entrada los resultados de +detect_clusters. Esta función también puede usar los +resultados de detect_perching_events para integrar los +eventos de posar que fueron identificados en los datos originales de +RFID o los sensores de infrarrojo, y puede usar los resultados de +preprocess_detections para integrar los eventos de +grabación de vídeos que no fueron incluidos en los clusters +detectados por detect_clusters. Esta función hace +inferencias de comportamiento de los eventos de movimiento, incluyendo +la dirección de movimiento, la magnitud de movimiento, la identidad de +individuo (cuando datos de RFID se encontraron en un +cluster), y en donde ocurrió el inicio de la secuencia de +movimiento (en la entrada o adentro del contenedor de nido). La función +devuelve una hoja de cálculo de inferencias de comportamiento y otros +metadatos sobre cada evento de movimiento, y estos resultados se pueden +usar para visualizaciones y análisis estadísticos.

  10. +
+


La figura 4 del manuscrito de ABISSMAL con un pipeline con las 5 funciones primarias +* Esta figura es de Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and +E.A. Hobson. 2023. Automated tracking of avian parental care. EcoEvoRxiv +preprint.

+

+Solucionar errores en línea +

+

Mientras escribes y ejecutas código encontrarás errores que a veces +pueden ser frustrantes. Experimentar y solucionar los errores que te +salen es una parte muy importante de tu proceso de aprendizaje en R (u +otros lenguajes de programación). Los errores surgen por diferentes +razones, incluyendo errores de mecanografía mientras escribes código, problemas +con las versiones de paquetes externos de R que instalaste, o +posiblemente por necesitar funciones de paquetes que no has instalado en +tu computadora. Los errores a veces pueden surgir por problemas con las +funciones de ABISSMAL que usarás en los siguientes tutoriales, o por +errores con el código en los tutoriales mismos.

+

Cuando experimentas un error con tu código, es importante intentar +solucionar el error independientemente antes de suponer que el error +nació de errores de las funciones de ABISSMAL o los tutoriales. Hay +muchos recursos en línea que puedes usar para solucionar errores comunes +o inusuales en R. Puedes empezar con copiar y pegar el mensaje de error +que ves en tu consola en un buscador, y así deberías de ver varias +opciones de foros públicos dónde otras personas han preguntado por y +solucionado errores similares. También deberías de poder de usar +herramientas de IA generativo como ChatGPT para buscar errores de mecanografía +u otros problemas con tu código. Otra opción útil es leer la +documentación de R para investigar si el error que ves esta relacionado +con un paquete o una función que puede ser una dependencia de +ABISSMAL.

+

Ya cuando hayas investigado cuidadosamente un error, y estas segura +que el error no se debe a un error de mecanografía o problemas con la +estructura de tus datos, luego puedes considerar si el error se debe a +un problema con las funciones de ABISSMAL o los tutoriales mismos, y +puedes reportar el error a las desarolladoras de ABISSMAL en GitHub (ver +la siguiente sección).

+

+Reportar errores en GitHub +

+

Es posible que encuentres errores mientras trabajas en cada tutorial, +incluyendo errores con código que no ejecuta, o código que devuelve +resultados incorrectos. Estos errores pueden manifestar desde las +funciones de ABISSMAL o con el código de algún tutorial. Si encuentras +un error en una función de ABISSMAL, puedes crear un “Issue” (un asunto +o problema) en el repositorio de GitHub. Para crear un “Issue” nuevo, +puedes seleccionar “New Issue” en la página de Issues en el +repositorio de ABISSMAL, y seguir las instrucciones en el esquema del +“Issue” nuevo para añadir la información que las desarrolladoras +necesitan para trabajar efectivamente en el error. También puedes añadir +la etiqueta de “bug” (o error) a tu “Issue”. Si encuentras un error con +los tutoriales puedes crear un “Issue” usando los mismos pasos, y +deberías de clarificar que el error está relacionado al código de algún +tutorial en particular.

+

En el siguiente tutorial, vamos a crear datos simulados de movimiento +de animales para aprender cómo usar las cinco funciones primarias de +ABISSMAL.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_02_Configuracion.Rmd b/R/vignettes/Tutorial_02_Configuracion.Rmd new file mode 100644 index 0000000..a3c2cce --- /dev/null +++ b/R/vignettes/Tutorial_02_Configuracion.Rmd @@ -0,0 +1,189 @@ +--- +title: "Tutorial 02: Configuración" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = FALSE) + +``` + +

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". + +

Resumen del tutorial y objetivos de aprendizaje

+ +En este segundo tutorial, vamos a configurar tu espacio de trabajo virtual para sesiones de código en R y para usar las funciones de ABISSMAL. Vas a aprender habilidades básicas de programación en R y mejores prácticas de la ciencia abierta para escribir código, incluyendo cómo: + +1. Usar archivos de RMarkdown +2. Limpiar tu ambiente global +3. Ejecutar código adentro de un "chunk" (trozo, pedazo) de RMarkdown +4. Aprender sobre funciones en R y su documentación +5. Instalar y acceder paquetes +6. Comentar tu código +7. Atajos de RStudio para escribir código +8. Crear y usar una carpeta o un directorio de trabajo + +

Uso de archivos de RMarkdown

+ +Cada tutorial en esta serie de tutoriales está disponible como un archivo de RMarkdown (extensión .Rmd) y también como un archivo de HTML que puedes abrir y ver en tu navegador favorito. Cada archivo de HTML fue generado para entrelazar el archivo de RMarkdown, o convertir el texto y el código junto con los resultados del código en un reporte en formato HTML. Puedes leer [la documentación de RMarkdown](https://rmarkdown.rstudio.com/lesson-1.html) o este tutorial por [Teresa Boca en RPubs](https://rpubs.com/tereboca/informe_rmakrdown) para aprender más sobre cómo usar RMarkdown para escribo código y generar reportes. + +Los archivos de RMarkdown facilitan el proceso de compartir tu código y tus resultados. Si nunca has usado RMarkdown, la mejor forma de completar los tutoriales será crear un archivo de RMarkdown nuevo para cada tutorial y escribir el código por ti misma. Aprenderás más si escribes el código y los comentarios (adentro y afuera de cada trozo de código) en tus propias palabras. También puedes abrir el reporte HTML de cada tutorial en tu navegador para tener una guía mientras escribes el código de cada tutorial en tu propio archivo de RMarkdown. Como otra alternativa, puedes abrir el archivo de RMarkdown del tutorial al lado de tu propio archivo de RMarkdown si añades una tercera columna a la configuración de tus paneles de RStudio. Para ello debes seleccionar lo siguiente: + +* "Tools" +* "Global Options" +* "Pane Layout" +* "Add Column" para añadir otro panel de fuente en una tercera columna +* Luego puedes abrir el tutorial original en un panel de fuente, y tu propio archivo de RMarkdown en el otro panel de fuente + +Si ya tienes experiencia con escribir código en R y usar archivos de RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial y ejecutar el código adentro de cada trozo (chunk) para completar cada tutorial (con algunas modificaciones de los directorios para usar los directorios y archivos de tu computadora). Si quieres preservar el código original en cada archivo de RMarkdown, puedes crear una copia de cada tutorial y modificar las copias mientras completas cada tutorial. + +

Limpiar tu ambiente global

+ +Tu ambiente global es tu espacio virtual en R, y puede contener paquetes y objectos diferentes que facilitarán tus objetivos de programación y/o analizar datos. Puedes ver los paquetes y objetos que existen en tu ambiente con hacer clic en la pestaña de ambiente o "Environment" en el mismo panel que incluye las pestañas de "History" y "Connections". + +Si iniciaste una sesión nueva de RStudio puede que tu ambiente global esté vacío. Pero si estás trabajando en una sesión vieja, o en un ambiente de un proyecto de R (RProject), tu ambiente global ya puede contener paquetes y objetos. Limpiar tu ambiente global es una buena práctica a seguir cada vez que empieces una sesión de código. Si no limpias tu ambiente global, incluso cuando uses el mismo código entre sesiones, corres el riesgo de usar versiones viejas de objetos que no reflejan los cambios más recientes en tu código. + +Puedes limpiar tu ambiente global directamente de la interfaz de RStudio con hacer clic en el ícono de la escoba debajo de la pestaña de "Environment" (haz clic en "Yes" con "hidden objects" para limpiar objetos escondidos también). + +También puedes limpiar tu ambiente global ejecutando el código que se detalla abajo. Puedes ejecutarlo dentro del siguiente trozo de formas diferentes: + +* Haz clic en el ícono de la flecha verde en la parte superior derecha del trozo de código para ejecutar solamente el código en este trozo + +* Puedes ubicar tu cursor (y hacer clic) en cualquiera posición adentro de la línea de código, y luego navegar al ícono de "Run" en la parte superior derecha del panel de fuente que tiene un cuadro blanco y una flecha verde. En el menú desplegable, selecciona "Run Selected Lines" o "Run Current Chunk" (para usar los atajos de cada comando, ve los siguientes dos puntos). "Run Selected Lines" ejecutará la línea de código en donde esté tu cursor, y "Run Current Chunk" ejecutará todo el código adentro del trozo (independientemente de la posición de tu cursor) + +* Puedes ubicar tu cursor (y hacer clic) en cualquiera posición adentro de la línea de código, y usar el atajo de "Ctrl" + "Enter" para ejecutar la línea de código donde esté tu cursor + +* Para ejecutar todo el código en el trozo (chunk), puedes usar el atajo "Ctrl" + "Shift" + "Enter" + +El primer atajo arriba (para ejecutar una línea de código a la vez), es muy útil para poder ver los resultados de cada línea de código y buscar errores. +```{r} + +rm(list = ls()) + +``` + +El código arriba para limpiar tu ambiente global es una expresión anidada con dos funciones: `rm()` y `ls()`. La notación de `()` se usa para funciones en R. Las funciones son operaciones que puedes aplicar en tu propio código usando el nombre de una función específica. R tiene una colección de funciones básicas que puedes acceder sin necesitar un paquete específico, incluyendo las dos funciones arriba (`rm()` y `ls()`). + +

Acceder documentación de funciones de R

+ +Puedes acceder la documentación para las funciones que usaste arriba con hacer clic en la pestaña de "Help" en el panel que incluye "Files" y "Plots", o puedes escribir el nombre de la función en la barra de búsqueda en el panel de "Help". También puedes acceder la documentación de funciones ejecutando el siguiente código: +```{r} + +?rm + +``` + +```{r} + +?ls + +``` + +La documentación de cada función contiene secciones específicas que pueden ser útiles para entender el uso y el propósito de la función, sobre todo los argumentos de la función. Estos argumentos son los valores que la función requiere de la usuaria para poder guiar o modificar la operación. Muchas funciones tienen valores por defecto para algunos de sus argumentos que se usarán cuando no asignas valores específicos. + +Por ejemplo, la función `rm()` tiene un argumento `list()` (seguido por un `=` o signo de igual, que se usa para asignar un valor específico a un argumento). Necesitas proveer información después del argumento `list()` para que `rm()` limpie tu ambiente global de la forma que quieres. Para eliminar todo en tu ambiente global, estamos usando los resultados de la función `ls()`, que imprime los nombres de todos los objetos en tu ambiente global. + +

Instalar y cargar paquetes

+ +Después de limpiar tu ambiente global, necesitas configurar tu espacio virtual de trabajo para preparar tu sesión de código y empezar a analizar datos. Un paso importante es asegurarte de que puedes acceder funciones que necesitas pero que no están disponibles a través de la colección de funciones básicas de R. Por ejemplo, [el `tidyverse`](https://www.tidyverse.org/) es una colección de paquetes de R que provee funciones y expresiones básicas útiles para analizar datos. + +Si no has instalado el `tidyverse` en tu computadora local, necesitas instalar esta colección de paquetes para acceder estas funciones de análisis de datos. El código siguiente instala el `tidyverse` de [CRAN](https://cran.r-project.org/), el "Comprehensive R Archive Network" en línea que contiene miles de paquetes de R. +```{r} + +# Instala el tidyverse de CRAN +install.packages("tidyverse") + +``` + +En el trozo de arriba, añadí un comentario antes del código usando el símbolo de `#`, un signo numeral o `hashtag`. Cualquier texto que escribes después de un `hashtag` será ignorado cuando ejecutas tu código. Es buena práctica comentar tu código, sobre todo cuando estés aprendiendo cómo escribir código en R. Para biólogas, comentar tu código incluso cuando eres experta también es una muy buena práctica. Comentar tu código es una forma de documentar tu trabajo y sirve para hacer el código que publicas con manuscritos o herramientas más accesible y entendible para otros en la comunidad. + +Cuando hayas instalado el `tidyverse` en tu computadora no necesitas instalarlo otra vez (por ejemplo, la siguiente vez que abres RStudio ya estará disponible). Lo único que necesitas hacer cada vez que abres otra sesión de RStudio es cargar el paquete en tu ambiente global para poder acceder las funciones contenidas adentro de la colección de paquetes del `tidyverse`. +```{r} + +library(tidyverse) + +``` + +

Atajos para escribir código

+ +RStudio tiene varios atajos útiles para escribir código. Puedes encontrar estos atajos yendo a "Tools" en el menú de RStudio, y luego seleccionando "Keyboard Shortcuts Help", que debería de abrir una ventana nueva con todos los atajos por defecto en RStudio. Arriba aprendiste sobre unos atajos para ejecutar código dentro de un trozo (chunk) de RStudio. Otros atajos útiles son "Shift + Ctrl + C", que puedes usar para comentar o silenciar de una a múltiples líneas de código a la vez (es decir, convertir líneas de código en comentarios), y "Ctrl + Alt + I", que automáticamente crea un trozo nuevo de RMarkdown. + +RStudio también contiene un atajo de autocompletar usando el tabulador (la tecla que dice "tab"). Por ejemplo, en el trozo de abajo, después de escribir `libr` y hacer clic en el tabulador, deberías de poder ver una ventana pequeña que demuestra todas las funciones, paquetes u objetos disponibles que empiezan en el patrón "libr". Puedes usar las teclas con flechas (arriba, abajo, etc) para seleccionar la opción que quieres, y "Enter" para completar la línea (por ejemplo, para escribir `library()` para cargar un paquete). +```{r eval = FALSE} + +libr + +``` + +

Resolver frases incompletas en la consola

+ +Es importante vigilar el panel de la consola mientras escribes y ejecutas código en RStudio. El símbolo de `>` (o "mayor que") en la consola significa que la consola terminó de ejecutar código y está lista para otra operación. Cuando ves el símbolo de `+` (o "más") en la consola, este símbolo indica que la frase de código que acabas de ejecutar no está completa. Las frases incompletas de código surgen de errores de mecanografía, como cuando te faltó abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de una frase incompleta de código: +```{r eval = FALSE} + +library(tidyverse + +``` + +Deberías de ver que el símbolo de `+` aparece en la consola cuando ejecutas el código arriba, porque te faltó un paréntesis para cerrar la función `library()`. Tienes dos opciones para resolver este problema. Primero, si sabes que símbolo te falta para completar la frase, puedes escribir este símbolo directamente en la consola y terminar de ejecutar el código con seleccionar "Enter". La segunda opción que tienes es hacer clic en la consola y luego seleccionar "Esc", que va a borrar la frase incompleta de la consola y reiniciar la consola para que puedas ejecutar más código (después de corregir tu error en el código en el panel de la fuente). Es buena práctica vigilar la consola para revisar que el código que ejecutas produce resultados o errores. Si ejecutas muchas frases de código a la vez y no observas los resultados que esperas en la consola, puede que tengas una frase incompleta por allí, y sería mejor limpiar la consola y revisar bien tu código antes de ejecutarlo otra vez. + +

Crear tu directorio de trabajo

+ +El siguiente paso en el proceso de configurar tu espacio virtual de trabajo, es decidir donde estará tu directorio de trabajo para tu sesión de código. Un directorio es una ubicación física en tu computadora (o una carpeta) donde R va a buscar archivos para leer o cargar datos. Cuando escribes datos de R como archivos físicos, estos archivos físicos se crearán en tu directorio de trabajo. + +Puedes usar la función `getwd()` para revisar tu directorio actual de trabajo. +```{r eval = TRUE} + +getwd() + +``` + +Mi directorio de trabajo por defecto es la carpeta en mi computadora donde guardé este archivo de RMarkdown. Para trabajar en un directorio aparte que contiene solo los datos generados en estos tutoriales, puedes crear un directorio nuevo o carpeta nueva en tu computadora: +```{r} + +?dir.create + +dir.create("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") + +``` + +En el código arriba, deberías de reemplazar el `path`, o la combinación de directorios (en este ejemplo, el `path` es "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") para que represente la ubicación en tu computadora donde quieres guardar la carpeta nueva que se llamará `ABISSMAL_tutoriales` (u otro nombre que prefieras). Si usas el sistema operativo de Windows, tienes que cambiar la dirección de los símbolos de barra porque el `path` que aparece en Windows para una carpeta va a tener barras invertidas (`\`) y R acepta solo barras inclinadas (`/`) en un `path`. + +Es común configurar tu directorio de trabajo con la función `setwd()` según cursos preliminares de programación en R. Sin embargo, es buena práctica evitar usar `setwd()` en código que quieres compartir con colaboradores o código que quieres compartir con la comunidad en general siguiendo la filosofía de ciencia abierta, por ejemplo cuando publicas un articulo o compartes una nueva herramienta. Usar `setwd()` cuando compartes tu código es suponer que todos los que van a usar tu código tienen el mismo directorio de trabajo en su computadora y esto es muy poco probable. Hay otras formas en que puedes especificar tu directorio de trabajo a través de tu código sin depender de `setwd()`. + +Por ejemplo, digamos que quieres crear una copia del primer tutorial y luego guardar esa copia adentro del directorio que creamos arriba. Podemos usar funciones de la colección base de R para copiar y guardar este archivo al directorio correcto de trabajo. Para evitar errores, vas a necesitar actualizar los `paths` abajo para que representen la carpeta en tu computadora con los tutoriales, y también tu directorio de trabajo: +```{r} + +file.copy( + from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Tutorial_01_Introduccion.Rmd", + to = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduccion_copy.Rmd" + ) + +``` + +En el código arriba especificamos dos argumentos a la función `file.copy()`. El primer argumento especifica la ubicación del archivo que queremos copiar ("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/") y el nombre del archivo mismo ("Tutorial_01_Introduccion.Rmd"). El segundo argumento especifica la ubicación donde queremos guardar la copia que vamos a crear de este archivo ("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/") y un nombre nuevo para la copia que vamos a crear ("Tutorial_01_Introduccion_copy.Rmd"). Los `paths` en cada argumento están entrecomillas en cada argumento, para indicar que la información entrecomillas se debería de tratar como el tipo de datos `character` en R, que es un término formal para la información en formato de texto. + +El resultado en la consola debería de ser "[1] TRUE" si el archivo se copió y se guardó correctamente después de que ejecutaste el código. Puedes revisar que la función ejecutó bien navegando a la carpeta donde se debió de haber guardado el archivo copiado, y ver que la copia existe. También puedes usar la función base de R `list.files()` desde RStudio para revisar si el archivo que copiaste existe en tu directorio de trabajo. El resultado de `list.files()` es una lista de todos los archivos adentro de tu directorio de trabajo y esta lista debería de contener un solo archivo: "Tutorial_01_Introduccion_copy.Rmd". +```{r} + +list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales") + +``` + +El código arriba es un ejemplo sobre cómo puedes especificar tu directorio de trabajo en el código que escribes sin tener que depender de `setwd()`. En los siguientes tutoriales vas a ver otros ejemplos sobre cómo puedes leer archivos o crear archivos en tu directorio de trabajo sin la función `setwd()`. Antes de empezar el siguiente tutorial, puedes borrar el archivo que copiaste a tu directorio de trabajo: +```{r} + +file.remove("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduction_copy.Rmd") + +``` + +En el siguiente tutorial vas a crear y manipular objetos en R que puedes usar para procesar y analizar datos con ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_02_Configuracion.html b/R/vignettes/Tutorial_02_Configuracion.html new file mode 100644 index 0000000..2b88043 --- /dev/null +++ b/R/vignettes/Tutorial_02_Configuracion.html @@ -0,0 +1,1937 @@ + + + + + + + + + + + + + + + +Tutorial 02: Configuración + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, +siguiendo las convenciones +de traducción de los Carpentries, incluyendo usar el género femenino +por defecto. Si encuentras errores de ortografía que impiden tu +habilidad de completar los tutoriales, por favor reporta los errores de +ortografía a GitHub usando los pasos en el primer tutorial para reportar +un “Issue”.

+

+Resumen del tutorial y objetivos de aprendizaje +

+

Vamos a configurar tu espacio de trabajo virtual para sesiones de +escribir código en R y usar las funciones de ABISSMAL en este segundo +tutorial. Vas a aprender habilidades básicas de programación en R y +buenas prácticas de la ciencia abierta para escribir código, incluyendo +cómo:

+
    +
  1. Usar archivos de RMarkdown
  2. +
  3. Limpiar tu ambiente global
  4. +
  5. Ejecutar código adentro de un “trozo” de RMarkdown
  6. +
  7. Aprender sobre funciones en R y su documentación
  8. +
  9. Instalar y acceder paquetes
  10. +
  11. Comentar tu código
  12. +
  13. Atajos de RStudio para escribir código
  14. +
  15. Crear y usar una carpeta o un directorio de trabajo
  16. +
+

+Usar archivos de RMarkdown +

+

Cada tutorial en esta serie de tutoriales esta disponible como un +archivo de RMarkdown (extensión .Rmd) y también como un archivo de HTML +que puedes abrir y ver en tu navegador por defecto. Cada archivo de HTML +fue generado por “tejer” el archivo de RMarkdown, o convertir el texto y +el código junto con los resultados del código en un reporte en formato +HTML. Puedes leer la documentación de +RMarkdown o este tutorial por Teresa Boca en +RPubs para aprender más sobre cómo usar RMarkdown para escribo +código y generar reportes.

+

Los archivos de RMarkdown facilitan el proceso de compartir tu código +y tus resultados. Si nunca has usado RMarkdown, la mejor forma de +completar los tutoriales será crear un archivo de RMarkdown nuevo para +cada tutorial y escribir el código por ti misma. Aprenderás más si +escribes el código y los comentarios (adentro y afuera de cada trozo de +código) en tus propias palabras. También puedes abrir el reporte HTML de +cada tutorial en tu navegador para tener una guía mientras escribes el +código de cada tutorial en tu propio archivo de RMarkdown. Como otra +alternativa, puedes abrir el archivo de RMarkdown del tutorial al lado +de con tu propio archivo de RMarkdown si añades una tercera columna a la +configuración de tus paneles de RStudio. Puedes seleccionar las +siguientes opciones:

+
    +
  • “Tools”
  • +
  • “Global Options”
  • +
  • “Pane Layout”
  • +
  • “Add Column” para añadir otro panel de fuente en una tercera +columna
  • +
  • Luego puedes abrir el tutorial original en un panel de fuente, y tu +propio archivo de RMarkdown en el otro panel de fuente
  • +
+

Si ya tienes experiencia con escribir código en R y usar archivos de +RMarkdown, puedes abrir el archivo original de RMarkdown de un tutorial +y ejecutar el código adentro de cada trozo para completar cada tutorial +(con algunas modificaciones de los directorios para especificar los +directorios y archivos en tu computadora). Si quieres preservar el +código original en cada archivo de RMarkdown, puedes crear una copia de +cada tutorial y modificar las copias mientras completas cada +tutorial.

+

+Limpiar tu ambiente global +

+

Tu ambiente global es tu espacio virtual en R, y puede contener +paquetes y objectos diferentes que facilitarán tus objetivos de +programación y/o analizar datos. Puedes ver los paquetes y objetos que +existen en tu ambiente con hacer clic en la pestaña de ambiente o +“Environment” en el mismo panel que incluye las pestañas de “History” y +“Connections”.

+

Si iniciaste una sesión nueva de RStudio puede que tu ambiente global +esté vacío. Pero si estás trabajando en una sesión vieja, o capaz que en +un ambiente de un proyecto de R (RProject), tu ambiente global ya puede +contener paquetes y objetos. Limpiar tu ambiente global es una buena +práctica seguir cada vez que empieces una sesión de escribir código. Si +no limpias tu ambiente global, incluso cuando uses el mismo código entre +sesiones, corres el riesgo de usar versiones viejas de objetos que no +reflejan los cambios más recientes en tu código.

+

Puedes limpiar tu ambiente global directamente de la interfaz de +RStudio con hacer clic en el ícono de la escoba debajo de la pestaña de +“Environment” (haz clic en “Yes” con “hidden objects” para también +limpiar objetos escondidos).

+

También puedes limpiar tu ambiente global con ejecutar código como se +detalla abajo. Puedes ejecutar el código adentro del siguiente trozo de +formas diferentes:

+
    +
  • Haz clic en el ícono de la flecha verde en la parte superior +derecha del trozo de código para ejecutar solamente el código en este +trozo

  • +
  • Puedes ubicar tu cursor (y hacer clic) en cualquiera posición +adentro de la línea de código, y luego navegar al ícono de “Run” en la +parte superior derecha del panel de fuente que tiene un cuadro blanco y +una flecha verde. En el menú desplegable, selecciona “Run Selected +Lines” o “Run Current Chunk” (o para usar los atajos de cada comando, ve +los dos puntos que siguen). “Run Selected Lines” ejecutará la línea de +código en donde esté tu cursor, y “Run Current Chunk” ejecutará todo el +código adentro del trozo (independientemente de la posición de tu +cursor)

  • +
  • Puedes ubicar tu cursor (y hacer clic) en cualquiera posición +adentro de la línea de código, y usar el atajo de “Ctrl” + “Enter” para +ejecutar la línea de código corriente donde esté tu cursor

  • +
  • Para ejecutar todo el código en el trozo, puedes usar el atajo +“Ctrl” + “Shift” + “Enter”

  • +
+

El primer atajo arriba, para ejecutar una línea de código a la vez, +es muy útil para poder ver los resultados de cada línea de código y +revisarlos por errores.

+
rm(list = ls())
+

El código arriba para limpiar tu ambiente global es una expresión +anidada con dos funciones: rm() y ls(). La +notación de () se usa para funciones en R. Las funciones +son operaciones que puedes aplicar en tu propio código con usar el +nombre de una función específica. R tiene una colección de funciones +base que puedes acceder sin necesitar un paquete específico, incluyendo +las dos funciones arriba (rm() y ls()).

+

+Acceder documentación de funciones de R +

+

Puedes acceder la documentación para las funciones que usaste arriba +con hacer clic en la pestaña de “Help” en el panel que incluye “Files” y +“Plots”, o puedes escribir el nombre de la función en la barra de +búsqueda en el panel de “Help”. También puedes acceder la documentación +de funciones con ejecutar este código:

+
?rm
+
?ls
+

La documentación de cada función contiene secciones específicas que +pueden ser útiles para entender el uso y el propósito de la función, +sobre todo los argumentos de la función. Estos argumentos son los +valores que la función requiere de la usuaria para poder guiar o +modificar la operación. Muchas funciones tienen valores por defecto para +algunos de sus argumentos que se usarán cuando no provees valores +específicos a los argumentos.

+

Por ejemplo, la función rm() tiene un argumento +list() (seguido por un = o signo igual, que se +usa para proveer un valor específico a un argumento). Necesitas proveer +información después del argumento list() para que +rm() limpie tu ambiente global de la forma que quieres. +Para eliminar todo en tu ambiente global, estamos usando los resultados +de la función ls(), que imprime los nombres de todos los +objetos en tu ambiente global.

+

+Instalar y cargar paquetes +

+

Después de limpiar tu ambiente global, todavía necesitas configurar +tu espacio virtual de trabajo para preparar para tu sesión de escribir +código para analizar datos. Un paso importante es asegurar que puedes +acceder funciones que necesitas para analizar datos pero que no son +disponibles a través de la colección de funciones bases de R. Por +ejemplo, el +tidyverse es una colección de paquetes de R que provee +funciones y expresiones útiles para analizar datos.

+

Si no has instalado el tidyverse en tu computadora +local, necesitas instalar esta colección de paquetes para acceder +funciones útiles para analizar datos. El código abajo instala el +tidyverse de CRAN, el “Comprehensive R Archive +Network” en línea que contiene miles de paquetes de R.

+
# Instala el tidyverse de CRAN
+install.packages("tidyverse")
+

En el trozo arriba, añadí un comentario arriba del código usando el +símbolo de #, un signo numeral o hashtag. +Cualquier texto que escribes después de un hashtag será +ignorado cuando ejecutas tu código. Es buena práctica comentar tu +código, sobre todo cuando estés aprendiendo cómo escribir código en R. +Para biólogas, comentar tu código incluso cuando eres experta también es +una muy buena práctica. Comentar tu código es una forma de documentar +tu trabajo y sirve para hacer el código que publicas con manuscritos o +herramientas más accesibles para otros en la comunidad.

+

Cuando hayas instalado el tidyverse en tu computadora no +necesitas instalarlo otra vez (por ejemplo, la siguiente vez que abres +RStudio). Lo que necesitas hacer cada vez que abres otra sesión de +RStudio es cargar el paquete en tu ambiente global para poder acceder +las funciones contenidas adentro de la colección de paquetes del +tidyverse.

+
library(tidyverse)
+

+Atajos para escribir código +

+

RStudio tiene varios atajo útiles para escribir código. Puedes +encontrar estos atajos con ir a “Tools” en el menú de RStudio, luego +seleccionar “Keyboard Shortcuts Help”, que debería de abrir una ventana +nueva con todos los atajos por defecto en RStudio. Arriba aprendiste +sobre unos atajos para ejecutar código adentro de un trozo de RStudio. +Algunos atajos útiles son “Shift + Ctrl + C”, que puedes usar para +comentar o silenciar de una a múltiples líneas de código a la vez (o +sea, convertir código a comentarios), y “Ctrl + Alt + I”, que +automáticamente crea un trozo nuevo de RMarkdown.

+

RStudio también contiene un atajo de autocompletar con el tabulador. +Por ejemplo, en el trozo abajo, después de escribir libr y +hacer clic en el tabulador, deberías de poder ver una ventana pequeña +que demuestra todas las funciones, paquetes, u objetos disponibles que +empiezan en el patrón “libr”. Puedes usar las teclas con flechas para +seleccionar la opción que quieres y hacer clic en “Enter” para completar +la línea (por ejemplo, para escribir library() para cargar +un paquete).

+
libr
+

+Resolver frases incompletas en la consola +

+

Es importante vigilar el panel de la consola mientras escribes y +ejecutas código en RStudio. El símbolo de > (o “mayor +que”) en la consola significa que la consola terminó de ejecutar código +y está lista para otra operación. Cuando ves el símbolo de ++ (o “más”) en la consola, este símbolo indica que la frase +de código que acabas de ejecutar no está completa. Las frases +incompletas de código surgen de errores de mecanografía, como cuando te faltó +abrir o cerrar los paréntesis en una función. Abajo hay un ejemplo de +una frase incompleta de código:

+
library(tidyverse
+

Deberías de ver que el símbolo de + aparece en la +consola cuando ejecutas el código arriba, porque te faltó un paréntesis +para cerrar la función library(). Tienes dos opciones para +resolver este problema. Primero, si sabes que símbolo te falta para +completar la frase, puedes escribir este símbolo directamente en la +consola y terminar de ejecutar el código con seleccionar “Enter”. La +segunda opción que tienes es hacer clic en la consola y luego +seleccionar “Esc”, que va a borrar la frase incompleta de la consola y +reiniciar la consola para que puedas ejecutar más código (después de +corregir tu error en el código en el panel de la fuente). Es buena +práctica vigilar la consola para revisar que el código que ejecutas +produce resultados. Si ejecutas muchas frases de código a la vez y no +observas los resultados que esperas en la consola, puede que tengas una +frase incompleta por allí, y sería mejor limpiar la consola y revisar +bien tu código antes de ejecutarlo otra vez.

+

+Crear tu directorio de trabajo +

+

El siguiente paso en el proceso de configurar tu espacio virtual de +trabajo es decidir donde estará tu directorio de trabajo para tu sesión +de escribir código. Un directorio es una ubicación física en tu +computadora (o una carpeta) donde R va a buscar archivos para leer o +cargar datos. Cuando escribes datas de R como archivos físicos, estos +archivos físicos se crearán en tu directorio de trabajo.

+

Puedes usar la función getwd() para revisar tu +directorio corriente de trabajo.

+
getwd()
+
## [1] "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes"
+

Mi directorio de trabajo por defecto es la carpeta en mi computadora +donde guardé este archivo de RMarkdown. Para trabajar en un directorio +aparte que contiene solo los datos generados en estos tutoriales, puedes +mejor crear un directorio nuevo o carpeta nueva en tu computadora:

+
?dir.create
+
+dir.create("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales")
+

En el código arriba, deberías de reemplazar el path, o +la combinación de directorios arriba (en este ejemplo, el +path es “/home/gsvidaurre/Desktop/ABISSMAL_tutoriales”) +para que represente la ubicación en tu computadora donde quieres guardar +la carpeta nueva que se llamará ABISSMAL_tutoriales (u otro +nombre que prefieres). Si usas el sistema operativo de Windows, tienes +que cambiar la dirección de los símbolos de barra porque el +path que aparece en Windows para una carpeta va a tener +barras invertidas (\) y R acepta solo barras inclinadas +(/) en un path.

+

Es común configurar tu directorio de trabajo con la función +setwd() en cursos preliminares de programación en R. Es +buena práctica evitar usar setwd() en código que quieres +compartir con colaboradores o código que quieres compartir con la +comunidad en general siguiendo la filosofía de ciencia abierta, por +ejemplo cuando publicas un articulo o compartes una nueva herramienta. +Usar setwd() cuando compartes tu código es suponer que +todos los que van a usar tu código tienen el mismo directorio de trabajo +en su computadora. Hay otras formas en que puedes especificar tu +directorio de trabajo a través de tu código sin depender de +setwd().

+

Por ejemplo, digamos que quieres crear una copia del primer tutorial +y luego guardar esa copia adentro del directorio que creamos arriba. +Podemos usar funciones de la colección base de R para copiar y guardar +este archivo al directorio correcto de trabajo. Para evitar errores, vas +a necesitar actualizar los paths abajo para que representen +la carpeta en tu computadora con los tutoriales, y también tu directorio +de trabajo:

+
file.copy(
+  from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Tutorial_01_Introduccion.Rmd",
+  to = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduccion_copy.Rmd"
+    )
+

En el código arriba especificamos dos argumentos a la función +file.copy(). El primer argumento especifica la ubicación +del archivo que queremos copiar +(“/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/”) y el +nombre del archivo mismo (“Tutorial_01_Introduccion.Rmd”). El segundo +argumento especifica la ubicación donde queremos guardar la copia que +vamos a crear de este archivo +(“/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/”) y un nombre nuevo para +la copia que vamos a crear (“Tutorial_01_Introduccion_copy.Rmd”). Los +paths en cada argumento están entrecomillas en cada +argumento, para indicar que la información entrecomillas se debería de +tratar como el tipo de datos character en R, que es un +termino formal para la información en formato de texto.

+

El resultado en la consola debería de ser “[1] TRUE” si el archivo se +copió y se guardó correctamente después de que ejecutaste el código. +Puedes revisar que la función ejecutó bien con navegar a la carpeta +donde se debió de haber guardado el archivo copiado y ver si esta copia +existe. También puedes usar la función base de R +list.files() para revisar desde RStudio si el archivo que +copiaste existe en tu directorio de trabajo. El resultado de +list.files() es una lista de todos los archivos adentro de +tu directorio de trabajo y esta lista debería de contener un solo +archivo: “Tutorial_01_Introduccion_copy.Rmd”.

+
list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales")
+

El código arriba es un ejemplo sobre cómo puedes especificar tu +directorio de trabajo en el código que escribes sin tener que depender +de setwd(). En los siguientes tutoriales vas a ver otros +ejemplos sobre cómo puedes leer archivos de o escribir archivos a tu +directorio de trabajo sin la función setwd(). Antes de +empezar el siguiente tutorial, puedes borrar el archivo que copiaste a +tu directorio de trabajo:

+
file.remove("/home/gsvidaurre/Desktop/ABISSMAL_tutoriales/Tutorial_01_Introduction_copy.Rmd")
+

En el siguiente tutorial vas a crear y manipular objetos en R que +puedes usar para procesar y analizar datos con ABISSMAL.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_03_SimularDatos.Rmd b/R/vignettes/Tutorial_03_SimularDatos.Rmd new file mode 100644 index 0000000..c218f0c --- /dev/null +++ b/R/vignettes/Tutorial_03_SimularDatos.Rmd @@ -0,0 +1,365 @@ +--- +title: "Tutorial 03: Simular Datos" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". + +

Resumen del tutorial y objetivos de aprendizaje

+ +En este tercer tutorial, vamos a crear datos simulados de movimientos de animales que fueron detectados por diferentes sensores. El proceso de simular estos datos sustituye el proceso de colección de datos que proporciona ABISSMAL para grabar datos de animales en vivo. Estos datos simulados te van a generar más oportunidades para practicar habilidades básicas de escribir código y tener control sobre la creación de estos datos te ayudará a entender los diferentes pasos del análisis de datos que se mencionan a continuación. Si quieres ver datos recolectados de pájaros con el `software` de ABISSMAL, y el código que usamos para analizar esos datos, puedes revisar el preprint del [manuscrito de metodos](https://ecoevorxiv.org/repository/view/6268/) que tiene enlaces a los datos mismos y al código que son públicamente accesibles. + +A través del proceso de simulción de datos en este tutorial, vas a continuar usando tus habilidades de programación que aprendiste en el segundo tutorial, y vas a aprender más sobre: + +1. Cómo crear objetos como vectores y `dataframes` +2. Tipos de datos en R +3. Cómo indexar y manipular objetos +4. Cómo usar frases condicionales +5. Expresiones de `pipe` en el `tidyverse` + +

Cargar paquetes

+ +En el tutorial anterior aprendiste cómo instalar el `tidyverse`, una colección de paquetes para la ciencia de datos. También aprendiste sobre directorios de trabajo y creaste un directorio nuevo en tu computadora para guardar archivos de datos o imágenes que vas a generar en los siguientes tutoriales. + +Cada vez que inicias un archivo nuevo de RMarkdown o R, es importante configurar tu espacio virtual antes de empezar a analizar datos. Con el trozo de abajo, vas a limpiar tu ambiente global y cargar el `tidyverse` usando código que viste en el segundo tutorial, pero ahora todo el código está combinado en un solo chunk (trozo). +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Limpia tu ambiente global + +library(tidyverse) # Carga la colección de paquetes en el tidyverse + +``` + +

Crear un objeto de `path` (ubicación)

+ +El siguiente paso será especificar la ubicación de tu directorio de trabajo. En el segundo tutorial, usaste un `string`, o una secuencia de caracteres entrecomillas para especificar el `path` de tu directorio de trabajo mientras usabas funciones diferentes. En vez de copiar y pegar la misma secuencia de caracteres o "string" cada vez que quieres usar este `path`, es más eficiente almacenar esta secuencia de caracteres (que especifica el `path` de tu directorio de trabajo) adentro de un objeto nuevo, y luego usar el nombre del objeto cuando necesitas especificar el `path`. + +Para crear un objeto de tu propio `path`, puedes escribir el nombre del objeto que quieres crear al lado izquierdo del trozo (sin comillas), luego los símbolos para crear un objeto en R (`<-`), y finalmente la información que quieres asignar a este objeto. En este caso, la información que quieres guardar adentro de este objeto es tu directorio de trabajo en el formato de un `string`, y esta información necesita estar entrecomillas. +```{r eval = TRUE} + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales" + +``` + +En el código arriba, creaste un objeto que se llama `path`. Puedes ver la información que contiene este objeto con escribir el nombre del objeto y ejecutar ese código en la consola: +```{r eval = TRUE} + +path + +``` + +También puedes ver el contenido de `path` con hacer clic en la pestaña de "Environment" y revisar la columna al lado derecho del nombre del objeto. Ahora puedes confirmar que `path` es un objeto nuevo que contiene información sobre tu directorio de trabajo y está disponible en tu ambiente global para más operaciones. + +Puedes practicar eliminar solamente el objeto `path` de tu ambiente global con escribir y ejecutar el código `rm(list = "path")`. Después de ejecutar este código, deberás de inicializar `path` otra vez usando el código de arriba. + +

Crear marcas de tiempo para detecciones simuladas de movimientos

+ +Los datos primarios que colecta ABISSMAL son marcas de tiempo que indican el momento en el tiempo cuando un sensor se activó y grabó movimiento. Muchas (pero no todas) de estas detecciones se pueden asignar a uno o más animales que se movieron cerca de un sensor, por ejemplo, cuando un pájaro entra a un contenedor de nido a través de una antena circular de RFID ("radio frequency identification") instalado en la entrada del contenedor. En los siguientes trozos de código, vamos a generar datos simulados que representan datos de detecciones grabados por el sensor de RFID y también sensores de infrarrojo. + +Digamos que estamos recolectando datos para dos pájaros adultos a través de sensores de ABISSMAL montados en un contenedor de nido. La antena de RFID está montada en la entrada del contenedor, y un par de sensores de infrarrojo, el par "externo", está montado enfrente de la antena de RFID para capturar movimientos afuera de la entrada del contenedor. Un segundo par de sensores de infrarrojo, el par "interno", está montado detrás de la antena de RFID para capturar movimientos adentro de la entrada del contenedor. En este ejemplo simulado, el par externo de sensores infrarrojo se va a activar cuando un pájaro entra al contenedor de nido, luego se activará la antena de RFID, y por último el par interno de sensores infrarrojo. Cuando un pájaro sale del contenedor, el par interno de sensores infrarrojo debería de activarse primero, luego la antena de RFID, y finalmente el par externo de sensores infrarrojo. Esto indicará la dirección del pájaro: entrando o saliendo. + +Empezaremos con crear un objeto que contiene las marcas de tiempo simuladas para la antena de RFID. Este objecto se llamara `rfid_ts` y va a contener cuatro marcas de tiempo en formato de horas:minutos:segundos. Cada marca de tiempo estará entrecomillas para indicar que estamos usando información de texto o secuencia de caracteres en el formato `string` de R. + +Vas a combinar estas marcas de tiempo adentro de un solo objeto usando la función `c()`. Esta función concatena valores separados por comas en un objecto de tipo vector o lista. Arriba creaste un objeto que se llama `path` sin usar `c()`, pero este objeto tenía un solo valor o elemento. Usar `c()` facilita combinar múltiples valores en un objeto como un vector que puede tener múltiples elementos. +```{r} + +# Crea un vector de cuatro marcas de tiempo de RFID o cuatro elementos en formato HH:MM:SS +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") + +``` + +Puedes ver las propiedades diferentes del objeto `rfid_ts`: +```{r} + +rfid_ts # Ejecuta el nombre del objeto para ver su contenido + +is.vector(rfid_ts) # Un valor binario indica si rfid_ts es un vector (TRUE) o no (FALSE) + +class(rfid_ts) # Un vector de tipo de dato `character` en R, o tipo `string` + +length(rfid_ts) # Este vector tiene cuatro elementos + +``` + +Continuemos simulando dos movimientos de entrada y dos movimientos de salida del contenedor. Podemos escoger marcas de tiempo para el par externo de sensores de infrarrojo que preceden las marcas de tiempo de RFID y marcas de tiempo del par interno de sensores infrarrojo que siguen las marcas de tiempo de RFID para simular un evento de entrada. Podemos simular eventos de salida con las marcas de tiempo en el orden opuesto (el par interno de sensores se activa primero, luego RFID, luego el par externo de sensores de infrarrojo). Vamos a separar las detecciones de cada sensor adentro de cada movimiento de entrada y salida por un segundo. Aquí "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. +```{r} + +# Simula marcas de tiempo para el par externo ("o_") e interno ("i_") de sensores infrarrojo para una entrada, una salida, y luego otra entrada y salida +o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") # externo +i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") # interno + +``` + +Los pájaros a veces posan en la entrada del contenedor usando la antena de RFID como percha, y los sensores deberían de colectar datos sobre este comportamiento. Puedes añadir eventos de posar a los datos simulados, y aquí vas a simular eventos de posar solamente con los datos de RFID. +```{r} + +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") + +``` + +En el código arriba, modificaste el objeto `rfid_ts` con la función `c()` para añadir diez más marcas de tiempo a este vector para tener un total de 14 elementos. Revisa la estructura del objeto modificado de `rfid_ts` usando la función `glimpse()`: +```{r} + +# Aquí puedes ver la estructura del objeto en un formato que incluye el tipo de dato en R ("chr", que es tipo `character` o `string`), el número de elementos ([1:14]), y los valores de los primeros elementos del vector +glimpse(rfid_ts) + +``` + +Otro tipo de información importante es simular ruido en las detecciones de los sensores. Por ejemplo, la antena de RFID puede fallar en detectar la etiqueta PIT ("passive integrated transponder") de un individuo, y los sensores de infrarrojo pueden activarse cuando los pájaros dejan material de nido colgando en la entrada del contenedor. En ambos casos, los sensores infrarrojo deberían de activar pero no la antena de RFID. +```{r} + +# Simula fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojo +# Estas fallas de detección del sensor de RFID surgieron en cuatro eventos simulados adicionales (dos entradas y dos salidas) +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") + +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) + +# Luego simula detecciones de ruido para el par externo de sensores infrarrojo +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") + +glimpse(o_irbb_ts) + +``` + +Acabas de crear datos simulados de movimientos de animales con datos que representan errores de detección, pero por el momento estos datos se encuentran en diferentes vectores separados y les faltan metadatos muy importantes. Por ejemplo, metadatos útiles incluyen información sobre la réplica experimental, la fecha, y para los datos de RFID, el código alfanumérico único de cada etiqueta PIT que fue detectada por la antena de RFID. Algunos de estos metadatos son críticos para los análisis más adelante, como la fecha y los códigos de las etiquetas PIT. + +Los vectores son estructuras útiles en R, pero una limitación de los vectores es que no puedes combinar diferentes tipos de datos en un solo objeto. Puedes intentar combinar diferentes tipos de datos en un vector: +```{r} + +# Crea un vector con los tipos `character`, `numeric`, y `binary` (o sea, datos de texto, valores numéricos, y valores binarios) +# El resultado se debería de imprimir directamente a la consola porque no estas guardando el resultado en un objeto +# Deberías de ver que todos los elementos se forzarán al tipo `character` o `string` entrecomillas +c("1", 1, TRUE, FALSE) + +# Ahora crea un vector con datos `numeric` y `binary` +# Deberías de poder ver que todos los elementos se forzarán al tipo `numeric`. Los valores de TRUE y FALSE se convirtieron a los valores numéricos que R usa por defecto para guardar información binaria (TRUE se convierte a 1, y FALSE se convierte a 0) +c(1, 1, TRUE, FALSE) + +``` + +Cuando intentas combinar los tipos de dato `character`, `numeric`, y `binary` en el mismo vector, todos los elementos del vector se convierten al tipo de dato `character`. Algo parecido resulta cuando intentas combinar datos de tipo `numeric` y `binary` en el mismo vector, pero en este caso, los valores se convierten a numérico. En este ejemplo, también aprendiste que los valores binarios TRUE y FALSE en R son equivalentes a los valores numéricos 1 y 0, respectivamente. + +

Crear vectores de metadatos

+ +Para los análisis que siguen, necesitas poder crear metadatos con diferentes tipos de datos, y luego combinar los datos primarios para cada sensor (las marcas de tiempo por sensor) con los metadatos importantes. Puedes empezar con crear vectores de metadatos para las marcas de tiempo de RFID, incluyendo información sobre la réplica experimental, la fecha, y la identidad de la etiqueta PIT para cada detección. + +Para crear un vector de metadatos sobre la réplica experimental, vas a usar la función `rep()` para repetir la información sobre la réplica experimental automáticamente, en vez de copiar y pegar la misma información varias veces. Para configurar el número de veces que la información sobre la réplica experimental se va a repetir, también vas a usar la función `length()` que calcula el tamaño del vector `rfid_ts` automáticamente, y comunicarle este resultado a `rep()`. Crear un vector de metadatos que tiene el mismo tamaño (largo) que el vector de `rfid_ts` será útil para combinar estos vectores en un solo objeto más adelante. +```{r} + +# La documentación nos dice que rep() espera dos argumentos, `x` y `time` +?rep + +# Crea un vector con información sobre la réplica experimental +# El argumento `x` contiene la información de metadatos que se va a repetir +# El argumento `times` especifica la cantidad de veces que esta información se va a repetir +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) + +glimpse(exp_rep) + +# También puedes ejecutar código sin escribir los nombres de los argumentos, siempre y cuando los argumentos se escriben en el mismo orden que la función espera: +exp_rep <- rep("Pair_01", length(rfid_ts)) + +glimpse(exp_rep) + +``` + +Usar `times = length(rfid_ts)` es mejor práctica que configurar el tamaño de `rfid_ts` manualmente (por ejemplo, `times = 14`). Configurar el valor de `times` manualmente es suponer que el objeto `rfid_ts` no ha cambiado adentro de una sesión de escribir código, o entre sesiones diferentes, y esto puede ser una suposición peligrosa. Cuando usas `times = length(rfid_ts)` te aseguras que el código arriba va a crear un vector de metadatos del mismo largo o tamaño que `rfid_ts` sin importar qué tantas modificaciones le hayas hecho a `rfid_ts`. + +Puedes también usar una frase condicional (condición) para confirmar que el vector de metadatos es del mismo tamaño que el vector de marcas de tiempo de RFID. Las frases condicionales puede ser útiles para revisar suposiciones en tu código, o para construir nuevos datos y funciones. +```{r} + +# Si esta condición se cumple, el resultado en la consola debería de ser "[1] TRUE" +length(rfid_ts) == length(exp_rep) + +``` + +En la frase condicional de arriba, estas usando los símbolos `==` para cuestionar si los dos vectores `rfid_ts` y `exp_rep` tienen la misma cantidad de elementos (si tienen el mismo largo o tamaño). + +También puedes usar los símbolos `!=` para preguntar si los dos vectores `rfid_ts` y `exp_rep` *no* tienen la misma cantidad de elementos (o sea, si *no* tienen el mismo largo): +```{r} + +# El resultado de esta frase debería de ser FALSE, porque estos vectores tienen el mismo largo +length(rfid_ts) != length(exp_rep) + +``` + +Como un ejemplo, puedes modificar `rfid_ts` para que tenga un número de elementos diferente a `exp_rep`. Abajo puedes ver algunas formas diferentes de filtrar o eliminar cuatro elementos del vector `rfid_ts` para que tenga diez elementos en total. +```{r} + +## Crea índices numéricos para filtrar un objeto + +# Puedes usar el símbolo `:` para crear una secuencia de números de los índices 5 a lo largo de rfid_ts +5:length(rfid_ts) + +# También puedes usar la función `seq()` para crear la misma secuencia de índices numéricos que ves arriba +seq(from = 5, to = length(rfid_ts), by = 1) + +# Si quieres filtrar elementos no consecutivos, puedes crear un vector de índices con la función `c()` +c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14) + +## Filtra un vector con índices numéricos + +# Cuando insertas cualquiera de las expresiones arriba dentro de los corchetes que vienen después del nombre del vector, puedes seleccionar los elementos del índice 5 a lo largo de rfid_ts, y así eliminar los primeros cuatro elementos +rfid_ts[5:length(rfid_ts)] + +rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] + +rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] + +# Puedes usar cualquiera de los métodos arriba para crear una secuencia de índices que quieras eliminar, y luego usar el símbolo `-` dentro de los corchetes para eliminar los elementos en esos índices particulares. Por ejemplo: +rfid_ts[-c(1:4)] # los números deberían de estar adentro de la función `c()` para que funcione el filtro de forma invertida + +``` + +Luego puedes revisar si esta versión modificada de `rfid_ts` es el mismo largo que `exp_rep`: +```{r} + +# Esta frase debería de resultar en TRUE, porque es verdad que estos vectores ya no tienen el mismo largo +length(rfid_ts[-c(1:4)]) != length(exp_rep) + +``` + +

Crear `dataframes` con datos primarios y metadatos

+ +Es importante combinar los metadatos con los datos primarios para análisis futuros, y puedes combinarlos usando un tipo de objeto que se llama un `dataframe` o cuadro de datos. Los `dataframes` son parecidos a las hojas de cálculo porque tienen dos dimensiones (filas y columnas), y puedes guardar múltiples tipos de datos diferentes en el mismo `dataframe`. También puedes guardar `dataframes` en hojas de cálculo o archivos físicos en tu computadora. + +Para combinar dos vectores en un solo `dataframe`, los vectores tienen que tener el mismo largo. Cuando intentas combinar dos vectores de largo diferentes, deberías de recibir un mensaje de error en la consola especificando que los dos argumentos no tienen el mismo número de filas: +```{r eval = FALSE} + +sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) + +# "Error in data.frame(exp_rep, rfid_ts[-c(1:4)]) : + # arguments imply differing number of rows: 14, 10" + +``` + +Ahora puedes usar los vectores enteros de `exp_rep` y `rfid_ts` para crear el `dataframe`, y los vectores se van a convertir en las columnas del `dataframe`: +```{r} + +sim_dats <- data.frame(exp_rep, rfid_ts) + +glimpse(sim_dats) + +``` + +Cuando revisas la estructura del objeto `sim_dats`, el `dataframe` nuevo, puedes ver que tiene 14 filas y dos columnas. Para cada columna (después del símbolo de "$"), puedes ver que el nombre de la columna (`exp_rep` y `rfid_ts`), el tipo de dato en cada columna (en este momento cada columna es del tipo `character`), y luego los valores de cada columna en las primeras filas del `dataframe`. + +Puedes cambiar los nombres de cada columna con añadir un nombre nuevo y el símbolo `=` antes de cada vector. Abajo, el vector `exp_rep` se convierte en la columna `replicate` con la réplica experimental y el vector `rfid_ts` se convierte en la columna `timestamps` con las marcas de tiempo. +```{r} + +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +glimpse(sim_dats) + +``` + +Podemos añadir metadatos adicionales a este `dataframe`, como información sobre la fecha de colección de datos. Puedes primero añadir una columna para el año usando el tipo de datos `double` en R que es un tipo de dato `numeric`. +```{r} + +sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts))) + +glimpse(sim_dats) + +``` + +La columna del año tiene un nombre extraño cuando añadimos una nueva columna usando `cbind()`, y si revisas el código de arriba, puedes ver que el nombre de esta columna se parece mucho al código que escribiste adentro de `cbind()`. Puedes usar la función `names()` e indexar con corchetes para cambiar el nombre extraño a un nombre mejor, como "year": +```{r} + +# Esta función devuelve un vector de los nombres de las columnas del dataframe +names(sim_dats) + +# Usa indexar con corchetes y la función ncol() para encontrar el último nombre entre los nombres de todas las columnas, porque esta última columna contiene la información sobre el año +ncol(sim_dats) # Hay 3 columnas en este dataframe + +# Esta expresión devuelve el nombre de la última columna +names(sim_dats)[ncol(sim_dats)] + +# Puedes sobrescribir el nombre de la última columna con un nombre nuevo +names(sim_dats)[ncol(sim_dats)] <- "year" + +# Confirma que el nombre de la columna de año se actualizó de la forma que esperas +names(sim_dats) +glimpse(sim_dats) + +``` + +

Crear `dataframes` usando el `tidyverse`

+ +En el código anterior, usaste código de R básico para añadir una columna nueva y actualizar el nombre de esa columna. Te tomó varias líneas de código para completar estas operaciones. Puedes reducir la cantidad de código que necesitas para estos pasos si eliminas las líneas de código que usaste para revisar las operaciones. Pero otra forma para reducir la cantidad de código que escribes para esta serie de operaciones es usar la notación y colección de funciones del `tidyverse`: +```{r} + +# Crear el dataframe de nuevo con dos columnas +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +# Usa el tidyverse para añadir el año como la tercera columna +sim_dats <- sim_dats %>% + dplyr::mutate( + year = rep(2023, length(rfid_ts)) + ) + +glimpse(sim_dats) + +``` + +Acabas de añadir una columna para el año con el nombre correcto en menos líneas de código. En la notación del `tidyverse`, el símbolo de `%>%` significa una operación de `pipe`, en que usas el objeto antes del símbolo de `%>%` como entrada para la función que sigue el símbolo de `%>%`. Arriba usaste el objeto de `sim_dats` como entrada para la función `mutate()`, que usaste para crear la columna `year`. + +La notación `dplyr::` antes de `mutate()` indica que la función `mutate()` se debería de acceder desde el paquete que se llama `dplyr`. Incluir el nombre del paquete con dos puntos repetidos dos veces es una notación importante que se debe usar cuando hay múltiples funciones accesibles en tu ambiente global con el mismo nombre. Por ejemplo, si usas otros paquetes aparte de `dplyr` que también tienen funciones que se llaman `mutate()`, y no especificas cual paquete quieres usar, puedes terminar con errores inmediatos (como cuando el código no se puede ejecutar). Incluso si el código ejecuta, usar la operación equivocada de otra función de `mutate()` puede introducir errores a tus análisis más adelante que son difíciles de identificar. + +Las operaciones de `piping` con el símbolo `%>%` (o un `pipe`) pueden simplificar el código que escribes porque no creas tantos objetos intermedios como cuando usas R base. Por esta misma razón, puede tomar mucha práctica solucionar errores con operaciones de `piping` cuando estas operaciones son largas y anidadas. Una forma útil para revisar resultados intermedios dentro de operaciones largas de `piping` es incluir la función `glimpse()` entre diferentes pasos de la operación: +```{r} + +# Crear el data frame otra vez con solo dos columnas +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +# Usa el tidyverse para añadir el año como la tercera columna +sim_dats %>% + glimpse() %>% # Ver la estructura de la primera versión de sim_dats + dplyr::mutate( + # La expresión nrow(.) significa "obtener el número de filas para el objeto actual". El objeto en este caso es sim_dats + year = rep(2023, nrow(.)) + ) %>% + glimpse() # Ver la estructura de la versión más reciente de sim_dats con la columna nueva de "year" + +``` + +En el código anterior, también aprendiste una forma nueva para repetir un valor adentro de una operación de `piping` con la notación `.` adentro de una función, que significa que la operación ejecutará con el objeto actual. En el ejemplo arriba, `.` se refiere al objeto `sim_dats` que se usó como entrada para la operación entera de `piping`. Como el símbolo `.` está adentro de la función `nrow()`, la función debería de devolver el número de filas de `sim_dat`. + +Puedes usar una operación parecida para añadir dos columnas al `dataframe` que contienen información del mes y día: +```{r} + +# Usar el tidyverse para añadir el año como la tercera columna +sim_dats %>% + glimpse() %>% # Ver la estructura de la versión original de sim_dats + dplyr::mutate( + year = 2023 + ) %>% + glimpse() %>% # Ver la estructura de la versión intermedia de sim_dats con la nueva columna del año + # También puedes añadir columnas para el mes ("month") y día ("day") + dplyr::mutate( + month = 08, + day = 01 + ) %>% + glimpse() # Ver la estructura de la versión final de sim_dats con las columnas adicionales con el mes y el día + +``` + +En el código anterior, añadiste dos columnas numéricas más al `dataframe` y lo hiciste sin necesitar usar la función `rep()` para repetir valores. Esto fue posible porque usaste un `dataframe` que ya existía como la entrada a las operaciones de `dplyr::mutate()`, y el único valor que especificaste para cada columna nueva del año, mes, y día se repitió automáticamente para llenar todas las filas en el `dataframe` para cada columna. Especificar un solo valor para una nueva columna puede ayudar a reducir la cantidad de código que escribes, pero sólo cuando de verdad quieres que el mismo valor se repita por todas las filas del `dataframe`. + +Como no guardaste estas modificaciones a `sim_dats` en un objeto, el resultado del código arriba se va a imprimir en la consola. En el siguiente tutorial, vas a guardar este `dataframe` de datos simulados de RFID y sensores infrarrojo en hojas de cálculo como archivos físicos en tu computadora. \ No newline at end of file diff --git a/R/vignettes/Tutorial_03_SimularDatos.html b/R/vignettes/Tutorial_03_SimularDatos.html new file mode 100644 index 0000000..571c46e --- /dev/null +++ b/R/vignettes/Tutorial_03_SimularDatos.html @@ -0,0 +1,2161 @@ + + + + + + + + + + + + + + + +Tutorial 03: Simular Datos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, +siguiendo las convenciones +de traducción de los Carpentries, incluyendo usar el género femenino +por defecto. Si encuentras errores de ortografía que impiden tu +habilidad de completar los tutoriales, por favor reporta los errores de +ortografía a GitHub usando los pasos en el primer tutorial para reportar +un “Issue”.

+

+Resumen del tutorial y objetivos de aprendizaje +

+

En este tercer tutorial, vamos a crear datos simulados de movimientos +de animales que fueron detectados por diferentes sensores. El proceso de +simular estos datos reemplaza el proceso de colección de datos que +provee ABISSMAL para grabar datos de animales en vivo. Generar estos +datos simulados te va a proveer más oportunidades para practicar +habilidades básicas de escribir código y tener control sobre la creación +de estos datos te ayudará entender los pasos diferentes de los análisis +de datos que siguen. Si quieres ver datos recolectados de pájaros con el +software de ABISSMAL, y el código que usamos para analizar +esos datos, puedes revisar el preprint del manuscrito de +metodos que tiene enlaces a los datos y el código que son +públicamente accesibles.

+

A través del proceso de simular datos en este tutorial, vas a +continuar usando tus habilidades de programación que aprendiste en el +segundo tutorial, y vas a aprender sobre:

+
    +
  1. Crear objetos como vectores y dataframes
  2. +
  3. Tipos de datos en R
  4. +
  5. Indexar y manipular objetos
  6. +
  7. Usar frases condicionales
  8. +
  9. Expresiones de pipe en el tidyverse
  10. +
+

+Cargar paquetes +

+

En el tutorial anterior instalaste el tidyverse, una +colección de paquetes para la ciencia de datos. También aprendiste sobre +directorios de trabajo y creaste un directorio nuevo en tu computadora +para guardar archivos de datos o imágenes que vas a generar en los +siguientes tutoriales.

+

Cada vez que inicies un archivo nuevo de RMarkdown o R, es importante +configurar tu espacio virtual antes de empezar a analizar datos. En el +trozo abajo, vas a limpiar tu ambiente global y cargar el +tidyverse usando código que viste en el segundo tutorial, +pero ahora todo el código está combinado en un solo trozo.

+
rm(list = ls()) # Limpia tu ambiente global
+
+library(tidyverse) # Carga la colección de paquetes en el tidyverse
+

+Crear un objeto de path +

+

El siguiente paso será especificar tu directorio de trabajo. En el +segundo tutorial, usaste un string, o una secuencia de +caracteres entrecomillas para indicar texto adentro de código de R, para +especificar el path de tu directorio de trabajo mientras +usabas funciones diferentes. En vez de copiar y pegar la misma secuencia +de caracteres cada vez que quieres usar este path, es más +eficiente guardar esta secuencia de caracteres (que especifica el +path de tu directorio de trabajo) adentro de un objeto +nuevo, y luego usar el nombre del objeto cuando necesitas especificar el +path.

+

Para crear un objeto de tu path, puedes escribir el +nombre del objeto que quieres crear al lado izquierdo del trozo (sin +comillas), luego los símbolos para crear un objeto en R +(<-), y luego la información que quieres asignar a este +objeto. En este caso, la información que quieres guardar adentro de este +objeto es tu directorio de trabajo en el formato de un +string, y esta información necesita estar +entrecomillas.

+
path <- "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales"
+

En el código arriba, creaste un objeto que se llama +path. Puedes ver la información que contiene este objeto +con escribir el nombre del objeto y ejecutar ese código en la +consola:

+
path
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_tutoriales"
+

También puedes ver el contenido de path con hacer clic +en la pestaña de “Environment” y revisar la columna al lado derecho del +nombre del objeto. Ahora puedes confirmar que path es un +objeto nuevo que contiene información sobre tu directorio de trabajo y +está disponible en tu ambiente global para más operaciones.

+

Puedes practicar eliminar solamente el objeto path de tu +ambiente global con escribir y ejecutar el código +rm(list = "path"). Después de ejecutar este código, +deberías de inicializar path otra vez usando el código +arriba.

+

+Crear marcas de tiempo para detecciones simuladas de movimientos +

+

Los datos primarios que colecta ABISSMAL son marcas de tiempo que +indican el momento en el tiempo cuando un sensor se activó y grabó +movimiento. Muchas (pero no todas) de estas detecciones se pueden +asignar a uno o más animales que se movieron cerca de un sensor, por +ejemplo, cuando un pájaro entra a un contenedor de nido a través de una +antena circular de RFID (“radio frequency identification”) montado en la +entrada del contenedor. En los siguientes trozos de código, vamos a +generar datos simulados que representan datos de detecciones grabados +por el sensor de RFID y también sensores de infrarrojo.

+

Digamos que estamos recolectando datos para dos pájaros adultos a +través de sensores de ABISSMAL montados en un contenedor de nido. La +antena de RFID está montada en la entrada del contenedor, y un par de +sensores de infrarrojo, el par “externo”, está montado en frente de la +antena de RFID para capturar movimientos afuera de la entrada del +contenedor. Un segundo par de sensores de infrarrojo, el par “interno”, +está montado detrás de la antena de RFID para capturar movimientos +adentro de la entrada del contenedor. En este ejemplo simulado, el par +externo de sensores infrarrojo va a activar cuando un pájaro entra al +contenedor de nido, luego la antena de RFID, y luego el par interno de +sensores infrarrojo. Cuando un pájaro sale del contenedor, el par +interno de sensores infrarrojo debería de activar primero, luego la +antena de RFID, y luego el par externo de sensores infrarrojo.

+

Empezaremos con crear un objeto que contiene las marcas de tiempo +simuladas para la antena de RFID. Este objecto se llamara +rfid_ts y va a contener cuatro marcas de tiempo en formato +de horas:minutos:segundos. Cada marca de tiempo estará entrecomillas +para indicar que estamos usando información de texto o secuencia de +caracteres en el formato string de R.

+

Vas a combinar estas marcas de tiempo adentro de un solo objeto +usando la función c(). Esta función concatena valores +separados por comas en un objecto de tipo vector o lista. Arriba creaste +un objeto que se llama path sin usar c(), pero +este objeto tenía un solo valor o elemento. Usar c() +facilita combinar múltiples valores en un objeto como un vector que +puede tener múltiples elementos.

+
# Crea un vector de cuatro marcas de tiempo de RFID o cuatro elementos en formato HH:MM:SS
+rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00")
+

Puedes ver las propiedades diferentes del objeto +rfid_ts:

+
rfid_ts # Ejecuta el nombre del objeto para ver sus contenidos
+
## [1] "10:00:00" "10:05:00" "11:00:00" "11:05:00"
+
is.vector(rfid_ts) # Un valor binario indica si rfid_ts es un vector (TRUE) o no (FALSE)
+
## [1] TRUE
+
class(rfid_ts) # Un vector de tipo de dato `character` en R, o tipo `string`
+
## [1] "character"
+
length(rfid_ts) # Este vector tiene cuatro elementos
+
## [1] 4
+

Continuemos por simular dos movimientos de entrada y dos movimientos +de salida del contenedor. Podemos escoger marcas de tiempo para el par +externo de sensores de infrarrojo que preceden las marcas de tiempo de +RFID y marcas de tiempo del par interno de sensores infrarrojo que +siguen las marcas de tiempo de RFID para simular un evento de entrada. +Podemos simular eventos de salida con las marcas de tiempo en el orden +opuesto (el par interno de sensores se activa primero, luego RFID, luego +el par externo de sensores de infrarrojo). Vamos a separar las +detecciones de cada sensor adentro de cada movimiento de entrada y +salida por un segundo. Aquí “IRBB” significa “infrared beam breakers” o +sensores de infrarrojo.

+
# Simula marcas de tiempo para el par externo ("o_") e interno ("i_") de sensores infrarrojo para una entrada, una salida, y luego otra entrada y salida
+o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") # externo
+i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") # interno
+

Los pájaros a veces posan en la entrada del contenedor usando la +antena de RFID como percha, y los sensores deberían de colectar datos +sobre este comportamiento. Puedes añadir eventos de posar a los datos +simulados, y aquí vas a simular eventos de posar solamente con los datos +de RFID.

+
rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05")
+

En el código arriba, modificaste el objeto rfid_ts con +la función c() para añadir diez más marcas de tiempo a este +vector para tener un total de 14 elementos. Revisa la estructura del +objeto modificado de rfid_ts usando la función +glimpse():

+
# Aquí puedes ver la estructura del objeto en un formato que incluye el tipo de dato en R ("chr", que es tipo `character` o `string`), el número de elementos ([1:14]), y los valores de los primeros elementos del vector
+glimpse(rfid_ts)
+
##  chr [1:14] "10:00:00" "10:05:00" "11:00:00" "11:05:00" "08:00:00" ...
+

Otro tipo de información importante es simular ruido en las +detecciones de los sensores. Por ejemplo, la antena de RFID puede fallar +en detectar la etiqueta PIT (“passive integrated transponder”) de un +individuo, y los sensores de infrarrojo pueden activarse cuando los +pájaros dejan material de nido colgando en la entrada del contenedor. En +ambos casos, los sensores infrarrojo deberían de activar pero no la +antena de RFID.

+
# Simula unas fallas de detección de la antena de RFID a través de ambos pares de sensores infrarrojo
+# Estas fallas de detección del sensor de RFID surgieron en cuatro eventos simulados adicionales (dos entradas y dos salidas)
+o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
+i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24")
+
+glimpse(o_irbb_ts)
+
##  chr [1:8] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+
glimpse(i_irbb_ts)
+
##  chr [1:8] "10:00:01" "10:04:59" "11:00:01" "11:04:59" "06:05:04" ...
+
# Luego simula unas detecciones de ruido para el par externo de sensores infrarrojo
+o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11")
+
+glimpse(o_irbb_ts)
+
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+

Acabas de crear datos simulados de movimientos de animales con unas +detecciones que representan errores de detección, pero por el momento +estos datos se encuentran en diferentes vectores separados y les faltan +metadatos muy importantes. Por ejemplo, metadatos útiles que nos faltan +incluyen información sobre la réplica experimental, la fecha, y para los +datos de RFID, el código alfanumérico único de cada etiqueta PIT que fue +detectada por la antena de RFID. Algunos de estos metadatos son críticos +para los análisis más adelante, como la fecha y los códigos de las +etiquetas PIT.

+

Los vectores son estructuras útiles en R, pero una limitación de los +vectores es que no puedes combinar diferentes tipos de datos en un solo +objeto. Puedes intentar combinar diferentes tipos de datos en un +vector:

+
# Crea un vector con los tipos `character`, `numeric`, y `binary` (o sea, datos de texto, valores numéricos, y valores binarios)
+# El resultado se debería de imprimir directamente a la consola porque no estas guardando el resultado en un objeto
+# Deberías de ver que todos los elementos se forzarán al tipo `character` o `string` entrecomillas
+c("1", 1, TRUE, FALSE)
+
## [1] "1"     "1"     "TRUE"  "FALSE"
+
# Ahora crea un vector con datos `numeric` y `binary`
+# Deberías de poder ver que todos los elementos se forzarán al tipo `numeric`. Los valores de TRUE y FALSE se convirtieron a los valores numéricos que R usa por defecto para guardar información binaria (TRUE se convierte a 1, y FALSE se convierte a 0)
+c(1, 1, TRUE, FALSE)
+
## [1] 1 1 1 0
+

Cuando intentas combinar los tipos de dato character, +numeric, y binary en el mismo vector, todos +los elementos del vector se convierten al tipo de data +character. Algo parecido resulta cuando intentas combinar +datos de tipo numeric y binary en el mismo +vector, pero en este caso, los valores se convierten a numérico. En este +ejemplo, también aprendiste que los valores binarios TRUE y FALSE en R +son equivalentes a los valores numéricos 1 y 0, respectivamente.

+

+Crear vectores de metadatos +

+

Para los análisis que siguen, necesitas poder crear metadatos con +diferentes tipos de datos, y luego combinar los datos primarios para +cada sensor (las marcas de tiempo por sensor) con los metadatos +importantes. Puedes empezar con crear vectores de metadatos para las +marcas de tiempo de RFID, incluyendo información sobre la réplica +experimental, la fecha, y la identidad de la etiqueta PIT para cada +detección.

+

Para crear un vector de metadatos sobre la réplica experimental, vas +a usar la función rep() para repetir la información sobre +la réplica experimental automáticamente, en vez de copiar y pegar las +misma información varias veces. Para configurar el número de veces que +la información sobre la réplica experimental se vaya a repetir, también +vas a usar la función length() para calcular lo largo del +vector rfid_ts automáticamente, y comunicarle este +resultado a rep(). Crear un vector de metadatos que tiene +el mismo largo que el vector de rfid_ts será útil para +combinar estos vectores en un solo objeto más adelante.

+
# La documentación nos dice que rep() espera dos argumentos, `x` y `time`
+?rep
+
+# Crea un vector con información sobre la réplica experimental
+# El argumento `x` contiene la información de metadatos que se va a repetir
+# El argumento `times` especifica la cantidad de veces que esta información se va a repetir
+exp_rep <- rep(x = "Pair_01", times = length(rfid_ts))
+
+glimpse(exp_rep)
+
##  chr [1:14] "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
+
# También puedes ejecutar código sin escribir los nombres de los argumentos, siempre y cuando los argumentos se escriben en el mismo orden que la función espera:
+exp_rep <- rep("Pair_01", length(rfid_ts))
+
+glimpse(exp_rep)
+
##  chr [1:14] "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
+

Usar times = length(rfid_ts) es mejor práctica que +configurar lo largo de rfid_ts manualmente (por ejemplo, +times = 14). Configurar el valor de times +manualmente es suponer que el objeto rfid_ts no ha cambiado +adentro de una sesión de escribir código, o entre sesiones diferentes, y +esto puede ser una suposición peligrosa. Cuando usas +times = length(rfid_ts) te aseguras que el código arriba va +a crear un vector de metadatos del mismo largo que rfid_ts +sin importar qué tantas modificaciones le hayas hecho a +rfid_ts en el código arriba.

+

Puedes también usar una frase condicional para confirmar que el +vector de metadatos es del mismo largo que el vector de marcas de tiempo +de RFID. Las frases condicionales puede ser útiles para revisar +suposiciones en tu código, o para construir nuevos datos y +funciones.

+
# Si esta condición se cumple, el resultado en la consola debería de ser "[1] TRUE"
+length(rfid_ts) == length(exp_rep)
+
## [1] TRUE
+

En la frase condicional arriba, estas usando los símbolos +== para preguntar si los dos vectores rfid_ts +y exp_rep tienen la misma cantidad de elementos (si tienen +el mismo largo).

+

También puedes usar los símbolos != para preguntar si +los dos vectores rfid_ts y exp_rep no +tienen la misma cantidad de elementos (o sea, si no tienen el +mismo largo):

+
# El resultado de esta frase debería de ser FALSE, porque estos vectores tienen el mismo largo
+length(rfid_ts) != length(exp_rep)
+
## [1] FALSE
+

Como un ejemplo, puedes modificar rfid_ts para que tenga +un número de elementos diferente a exp_rep. Abajo puedes +ver algunas formas diferentes de filtrar o eliminar cuatro elementos del +vector rfid_ts para que tenga diez elementos en total.

+
## Crea índices numéricos para filtrar un objeto
+
+# Puedes usar el símbolo `:` para crear una secuencia de números de los índices 5 a lo largo de rfid_ts 
+5:length(rfid_ts)
+
##  [1]  5  6  7  8  9 10 11 12 13 14
+
# También puedes usar la función `seq()` para crear la misma secuencia de índices numéricos que ves arriba
+seq(from = 5, to = length(rfid_ts), by = 1)
+
##  [1]  5  6  7  8  9 10 11 12 13 14
+
# Si quieres filtrar elementos no consecutivos, puedes crear un vector de índices con la función `c()`
+c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)
+
##  [1]  1  3  5  6  8 10 11 12 13 14
+
## Filtra un vector por índices numéricos
+
+# Cuando insertas cualquiera de las expresiones arriba adentro de los corchetes que vienen después del nombre del vector, puedes seleccionar los elementos del índice cinco a lo largo de rfid_ts, y así eliminar los primeros cuatro elementos 
+rfid_ts[5:length(rfid_ts)]
+
##  [1] "08:00:00" "08:00:01" "08:00:02" "08:00:03" "11:30:00" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+
rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)]
+
##  [1] "08:00:00" "08:00:01" "08:00:02" "08:00:03" "11:30:00" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+
rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)]
+
##  [1] "10:00:00" "11:00:00" "08:00:00" "08:00:01" "08:00:03" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+
# Puedes usar cualquier de los métodos arriba para crear una secuencia de índices que quieres eliminar, y luego usar el símbolo `-` adentro de los corchetes para eliminar los elementos en esos índices particulares. Por ejemplo:
+rfid_ts[-c(1:4)] # los números deberían de estar adentro de la función `c()` para que funcione este tipo de filtrar de forma invertida
+
##  [1] "08:00:00" "08:00:01" "08:00:02" "08:00:03" "11:30:00" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+

Luego puedes revisar si esta versión modificada de +rfid_ts es el mismo largo que exp_rep:

+
# Esta frase debería de resultar en TRUE, porque estos vectores ya no tienen el mismo largo
+length(rfid_ts[-c(1:4)]) != length(exp_rep)
+
## [1] TRUE
+

+Crear dataframes con datos primarios y metadatos +

+

Es importante combinar los metadatos con los datos primarios para +análisis futuros, y puedes combinarlos usando un tipo de objeto que se +llama un dataframe. Los dataframes son +parecidos a las hojas de cálculo porque tienen dos dimensiones (filas y +columnas), y puedes guardar múltiples tipos de datos diferentes en el +mismo dataframe. También puedes guardar +dataframes en hojas de cálculo o archivos físicos en tu +computadora.

+

Para combinar dos vectores en un solo dataframe, los +vectores tienen que tener el mismo largo. Cuando intentas combinar dos +vectores de largo diferentes, deberías de recibir un mensaje de error en +la consola especificando que los dos argumentos no tienen el mismo +número de filas:

+
sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)])
+
+# "Error in data.frame(exp_rep, rfid_ts[-c(1:4)]) : 
+  # arguments imply differing number of rows: 14, 10"
+

Ahora puedes usar los vectores enteros de exp_rep y +rfid_ts para crear el dataframe, y los +vectores se van a convertir en las columnas del +dataframe:

+
sim_dats <- data.frame(exp_rep, rfid_ts)
+
+glimpse(sim_dats)
+
## Rows: 14
+## Columns: 2
+## $ exp_rep <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_0…
+## $ rfid_ts <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00", "0…
+

Cuando revisas la estructura del objeto sim_dats, el +dataframe nuevo, puedes ver que tiene 14 filas y dos +columnas. Para cada columna (después del símbolo de “$”), puedes ver que +el nombre de la columna (aquí exp_rep y +rfid_ts), el tipo de dato en cada columna (en este momento +cada columna es del tipo character), y luego los valores de +cada columna en las primeras filas del dataframe.

+

Puedes cambiar los nombres de cada columna con añadir un nombre nuevo +y el símbolo = antes de cada vector. Abajo el vector +exp_rep se convierte en la columna replicate +con la réplica experimental y el vector rfid_ts se +convierte en la columna timestamps con las marcas de +tiempo.

+
sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts)
+
+glimpse(sim_dats)
+
## Rows: 14
+## Columns: 2
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+

Podemos añadir metadatos adicionales a este dataframe, +como información sobre la fecha de colección de datos. Puedes primero +añadir una columna para el año usando el tipo de datos +double en R que es un tipo de dato +numeric.

+
sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts)))
+
+glimpse(sim_dats)  
+
## Rows: 14
+## Columns: 3
+## $ replicate                    <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01…
+## $ timestamps                   <chr> "10:00:00", "10:05:00", "11:00:00", "11:0…
+## $ `rep(2023, length(rfid_ts))` <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

La columna del año tiene un nombre extraño cuando añadimos una nueva +columna usando cbind(), y si revisas el código de arriba, +puedes ver que el nombre de esta columna se parece mucho al código que +escribiste adentro de cbind(). Puedes usar la función +names() e indexar con corchetes para cambiar el nombre +extraño a un nombre mejor, como “year”:

+
# Esta función devuelve un vector de los nombres de las columnas del dataframe
+names(sim_dats)
+
## [1] "replicate"                  "timestamps"                
+## [3] "rep(2023, length(rfid_ts))"
+
# Usa indexar con corchetes y la función ncol() para encontrar el último nombre entre los nombres de todas las columnas, porque esta última columna contiene la información sobre el año
+ncol(sim_dats) # Hay 3 columnas en este dataframe
+
## [1] 3
+
# Esta expresión devuelve el nombre de la última columna
+names(sim_dats)[ncol(sim_dats)]
+
## [1] "rep(2023, length(rfid_ts))"
+
# Puedes sobrescribir el nombre de la última columna con un nombre nuevo
+names(sim_dats)[ncol(sim_dats)] <- "year"
+
+# Confirma que el nombre de la columna de año se actualizó de la forma que esperas
+names(sim_dats)
+
## [1] "replicate"  "timestamps" "year"
+
glimpse(sim_dats)
+
## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

+Crear dataframes usando el tidyverse +

+

En el código arriba, usaste código de R base para añadir una columna +nueva y actualizar el nombre de esa columna. Te tomó varias líneas de +código para completar estas operaciones. Puedes reducir la cantidad de +código que necesitas para estos pasos si eliminas las líneas de código +que usaste para revisar las operaciones. Pero otra forma para reducir la +cantidad de código que escribes para esta serie de operaciones es usar +la notación y colección de funciones del tidyverse:

+
# Crear el dataframe de nuevo con dos columnas
+sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts)
+
+# Usa el tidyverse para añadir el año como la tercera columna
+sim_dats <- sim_dats %>% 
+  dplyr::mutate(
+    year = rep(2023, length(rfid_ts))
+  )
+
+glimpse(sim_dats)
+
## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

Acabas de añadir una columna para el año con el nombre correcto en +menos líneas de código. En la notación del tidyverse, el +símbolo de %>% significa una operación de +pipe, en que usas el objeto antes del símbolo de +%>% como entrada para la operación o función que sigue +el símbolo de %>%. Arriba usaste el objeto de +sim_dats como entrada para la función +mutate(), que usaste para crear la columna +year.

+

La notación dplyr:: antes de mutate() +indica que la función mutate() se debería de acceder desde +el paquete que se llama dplyr. Incluir el nombre del +paquete con dos puntos repetidos dos veces es una notación importante +usar cuando hay múltiples funciones accesibles en tu ambiente global con +el mismo nombre. Por ejemplo, si usas otros paquetes aparte de +dplyr que también tienen funciones que se llaman +mutate(), y no especificas cual paquete quieres usar, +puedes terminar con errores inmediatos (como cuando el código no se +puede ejecutar). Incluso si el código ejecuta, usar la operación +equivocada de otra función de mutate() puede introducir +errores a tus análisis más adelante que son difíciles de +identificar.

+

Las operaciones de piping con el símbolo +%>% (o un pipe) pueden simplificar el +código que escribes porque no creas tantos objetos intermedios como +cuando usas R base. Por otro lado, por esta misma razón puede tomar +práctica solucionar errores con operaciones de piping +cuando estas operaciones son largas y anidadas. Una forma útil para +revisar resultados intermedios adentro de operaciones largas de +piping es incluir la función glimpse() entre +diferentes pasos de la operación:

+
# Crear el data frame otra vez con solo dos columnas
+sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts)
+
+# Usa el tidyverse para añadir el año como la tercera columna
+sim_dats %>%
+  glimpse() %>% # Ver la estructura de la primera versión de sim_dats
+  dplyr::mutate(
+    # La expresión nrow(.) significa "obtener el número de filas para el objeto actual". El objeto en este caso es sim_dats
+    year = rep(2023, nrow(.))
+  ) %>%
+  glimpse() # Ver la estructura de la versión más reciente de sim_dats con la columna nueva de "year"
+
## Rows: 14
+## Columns: 2
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

En el código arriba, también aprendiste una forma nueva para repetir +un valor adentro de una operación de piping con la notación +. adentro de una función, que significa que la operación +ejecutará con el objeto actual. En el ejemplo arriba, . se +refiere al objeto sim_dats que se usó como entrada para la +operación entera de piping. Como el símbolo . +está adentro de la función nrow(), la función debería de +devolver el número de filas de sim_dat.

+

Puedes usar una operación parecida para añadir dos columnas al +dataframe que contienen información del mes y día:

+
# Usar el tidyverse para añadir el año como la tercera columna
+sim_dats %>%
+  glimpse() %>% # Ver la estructura de la versión original de sim_dats
+  dplyr::mutate(
+    year = 2023
+  ) %>%
+  glimpse() %>% # Ver la estructura de la versión intermedia de sim_dats con la nueva columna del año
+  # También puedes añadir columnas para el mes ("month") y día ("day")
+  dplyr::mutate(
+    month = 08,
+    day = 01
+  ) %>% 
+  glimpse() # Ver la estructura de la versión final de sim_dats con las columnas adicionales con el mes y el día
+
## Rows: 14
+## Columns: 2
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## Rows: 14
+## Columns: 5
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+

En el código arriba, añadiste dos columnas numéricas más al +dataframe y lo hiciste sin necesitar usar la función +rep() para repetir valores. Esto fue posible porque usaste +un dataframe que ya existía como la entrada a las +operaciones de dplyr::mutate(), y el único valor que +especificaste para cada columna nueva del año, mes, y día se repitió +automáticamente para llenar todas las filas en el dataframe +para cada columna. Especificar un solo valor para una nueva columna +puede ayudar reducir la cantidad de código que escribes, pero sólo +cuando de verdad quieres que el mismo valor se repite por todas las +filas del dataframe.

+

Como no guardaste estas modificaciones a sim_dats en un +objeto, el resultado del código arriba se va a imprimir a la consola. En +el siguiente tutorial, vas a guardar este dataframe de +datos simulados de RFID y sensores infrarrojo a hojas de cálculo como +archivos físicos en tu computadora.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_04_GuardarDatos.Rmd b/R/vignettes/Tutorial_04_GuardarDatos.Rmd new file mode 100644 index 0000000..efd8d94 --- /dev/null +++ b/R/vignettes/Tutorial_04_GuardarDatos.Rmd @@ -0,0 +1,704 @@ +--- +title: "Tutorial 04: Guardar Datos" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". + +

Resumen del tutorial y objetivos de aprendizaje

+ +En este cuarto tutorial, vas a guardar hojas de cálculo de las detecciones simuladas de movimientos de animales en tu computadora. Vas a continuar usando habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades que incluyen: + +1. Indexar y filtrar `dataframes` +2. Revisar la estructura de `dataframes` con R base y el `tidyverse` +3. Guardar objetos de R como archivos físicos en tu computadora +4. Leer archivos de tu computadora a R +5. Escribir y probar bucles + +

Cargar paquetes e inicializar el `path` de tu directorio de trabajo

+ +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Limpia tu ambiente global + +library(tidyverse) # Carga la colección de paquetes del tidyverse + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo + +``` + +

Crear los datos simulados

+ +En el código abajo, vas a recrear los datos simulados de RFID y sensores infrarrojo del tutorial anterior. Aquí estamos combinando el código en menos trozos comparado con el tercer tutorial: +```{r} + +# Crea un vector de cuatro marcas de tiempo de RFID en formato HH:MM:SS +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") + +# Añade eventos de posar a los datos de RFID +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") + +glimpse(rfid_ts) + +``` + +Aquí "IRBB" significa "infrared beam breakers" o sensores de infrarrojo. +```{r} + +# Simula marcas de tiempo para los pares externos ("o_") e internos ("i_") de sensores infrarrojo para una entrada y una salida, y luego otra entrada y salida +o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") + +# Simula unos errores de detección por la antena de RFID en las marcas de tiempo de cada par de sensores infrarrojo +# Estos errores de detección surgieron en cuatro movimientos adicionales: dos entradas y dos salidas +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") + +# Simula unas detecciones de ruido en las marcas de tiempo del par externo de sensores infrarrojo +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") + +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) + +``` + +

Simula tres días de colección de datos de RFID

+ +En el código siguiente, vas a combinar el vector de marcas de tiempo de RFID que inicializaste arriba con metadatos en un `dataframe`. Estos metadatos van a incluir el año, el mes, y el día, y también una columna con los valores de dos etiquetas PIT (una etiqueta por individuo simulado), y una columna con información sobre el tipo de sensor. Deberías de reconocer partes de este código del tutorial anterior: +```{r} + +# Crea un vector para la réplica experimental +exp_rep <- rep(x = "Nest_01", times = length(rfid_ts)) + +# Crea un vector de las identidades de las etiquetas PIT +# Asigna las primeras cuatro detecciones de RFID al primer individuo, el primer evento de posar (cuatro detecciones) al primer individuo, y el segundo evento de posar (seis detecciones) al segundo individuo +# Estas tres expresiones de rep() están combinadas en un solo vector usando la función c() +PIT_tag <- c(rep("1357aabbcc", 4), rep("1357aabbcc", 4), rep("2468zzyyxx", 6)) + +# Crea el dataframe con los metadatos de réplica experimental y las marcas de tiempo +sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts) + +# Sobrescribe el dataframe con la versión modificada que tiene columnas para el año, el mes, y el día +sim_dats_rfid <- sim_dats_rfid %>% + dplyr::mutate( + year = 2023 + ) %>% + dplyr::mutate( + month = 08, + day = 01 + ) %>% + # Añade los metadatos de las etiquetas PIT en una columna nueva + dplyr::mutate( + PIT_tag = PIT_tag + ) %>% + dplyr::mutate( + sensor_id = "RFID" + ) + +glimpse(sim_dats_rfid) + +``` + +Ahora puedes usar este `dataframe` para simular el proceso de colección de datos a través de dos días más. Para crear observaciones (filas) de dos días adicionales, puedes adjuntar las filas de una copia modificada de `sim_dats_rfid` al objeto original de `sim_dats_rfid`. + +En el código siguiente, estás usando una operación `pipe` para usar `sim_dats_rfid` como entrada en `bind_rows()`, y con esta notación estás especificando que `sim_dats_rfid` es el objeto original al cual quieres adjuntar más filas. Luego el código dentro de `bind_rows()` especifica el `dataframe`, o las filas nuevas que quieres adjuntar a `sim_dats_rfid`. En este caso, el código adentro de `bind_rows()` asigna `sim_dats_rfid` a `dplyr::mutate()` para modificar la columna de `day` y representar un día adicional de colección de datos. Luego repites este proceso para añadir un tercer día de recolectar datos simulados: +```{r} + +sim_dats_rfid <- sim_dats_rfid %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 02 + ) + ) %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 03 + ) + ) + +glimpse(sim_dats_rfid) # Tres veces el número original de filas + +``` + +

Revisar columnas de `dataframe` con R base

+ +Acabas de revisar la estructura del `dataframe` para confirmar que los datos simulados tienen datos recolectados a través de tres días. También puedes revisar los valores únicos que están presentes en la columna de `day`. Abajo puedes ver una forma de revisar los valores únicos adentro de una columna de un `dataframe`, usando dos ejemplos diferentes de notación de R base para acceder columnas adentro de un `dataframe`: +```{r} + +# Escribir una expresión con el nombre de un objeto dataframe, un símbolo $, y el nombre de una columna te ayuda a acceder una columna a la vez en un dataframe. Una columna en un dataframe es un vector, por ende cuando ejecutas este código deberías de ver un vector de valores impreso en la consola +sim_dats_rfid$day + +# Puedes también acceder una columna en un dataframe indexando, si escribes dos pares de corchetes (ambos pares de abrir y cerrar) después del nombre del dataframe, y colocas el nombre de la columna entrecomillas adentro del par interno de corchetes +sim_dats_rfid[["day"]] + +# Puedes usar la función unique() para ver los valores únicos dentro de un vector, incluyendo una columna en un dataframe +unique(sim_dats_rfid$day) # Tres días, se ve bien + +unique(sim_dats_rfid[["day"]]) # Tres días, se ve bien + +``` + +

Revisar columnas en un `dataframe` con el `tidyverse`

+ +También puedes revisar valores únicos en una columna usando funciones del `tidyverse`. En la expresión de abajo, vas a usar una expresión de `pipe` para facilitar el `dataframe` `sim_dats_rfid` a la función `pull()`, que va a acceder la columna `day` del `dataframe` como un vector. Luego este vector de la columna `day` se va a usar como entrada a la función `unique()` para revisar los valores únicos del vector mismo. La función `unique()` no requiere un argumento adentro de los paréntesis porque ya recibió el valor de entrada que necesita a través de la operación de `piping`. +```{r} + +# Tres días, se ve bien +sim_dats_rfid %>% + pull(day) %>% + unique() + +``` + +

Simula tres días de recolectar datos de los sensores infrarrojo

+ +Ahora puedes repetir este proceso de crear un `dataframe` con metadatos para los datos de los sensores de infrarrojo. Dado que los sensores de infrarrojo no colectan información sobre la identidad única de individuos, vas a añadir columnas para el año, el mes, el día, y el tipo de sensor. También vas a simular la colección de datos para estos sensores a través de los mismos tres días que los datos simulados de RFID. +```{r} + +# Sobrescribe el vector exp_rep con un vector nuevo que tenga el mismo largo que los vectores o_irbb_ts y i_irbb_ts juntos +exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts)) + +# Añade las marcas de tiempo de ambos pares de sensores infrarrojo a la misma columna usando c() +sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = c(o_irbb_ts, i_irbb_ts)) + +sim_dats_irbb <- sim_dats_irbb %>% + dplyr::mutate( + year = 2023, + month = 08, + day = 01, + # Añade un identificador único para cada par de sensores + # Cada etiqueta única se repetirá por el tamaño del vector de marcas de tiempo de cada par de sensores infrarrojo + sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts))) + ) + +glimpse(sim_dats_irbb) + +sim_dats_irbb <- sim_dats_irbb %>% + bind_rows( + sim_dats_irbb %>% + dplyr::mutate( + day = 02 + ) + ) %>% + bind_rows( + sim_dats_irbb %>% + dplyr::mutate( + day = 03 + ) + ) + +glimpse(sim_dats_irbb) # Tres veces el número de filas + +# Tres días, se ve bien +sim_dats_irbb %>% + pull(day) %>% + unique() + +``` + +

Guarda un `dataframe` como un archivo físico

+ +Los `dataframes` que creas y manipulas en R se pueden guardar como archivos físicos en tu directorio de trabajo. Tienes muchas opciones diferentes para guardar `dataframes`, pero recomiendo que uses formato `.csv` porque este formato es compatible con R, Microsoft Word, y otros programas. Puedes usar la función `write.csv()` para guardar `dataframes` a hojas de cálculo `.csv` en tu computadora: +```{r eval = FALSE} + +?write.csv + +``` + +Para escribir un archivo físico en tu directorio de trabajo, necesitas comunicarle a R 1) donde guardar el archivo y 2) el nombre del archivo que quieres crear. Puedes pasarle ambas piezas de información a `write.csv()` con combinar tu directorio de trabajo y el nombre del archivo usando la función `file.path()`. Para este ejemplo, vas a crear un archivo de prueba mientras practicas cómo usar `write.csv()`: +```{r} + +# Combina el path (camino o ubicación) a tu directorio de trabajo con el nombre del archivo que quieres escribir +# La función file.path() combinará ambas piezas de información en un solo path para este archivo +rfid_file <- file.path(path, "test_file.csv") + +# Este objeto contiene la ubicación dónde vas a guardar el archivo, y luego el nombre del archivo +rfid_file + +``` + +Luego puedes proveer el `dataframe` a `write.csv()` usando un `pipe` y puedes especificar información adicional para crear el archivo `.csv`, como si quisieras añadir una columna adicional de identidades numéricas de las filas: +```{r eval = FALSE} + +sim_dats_rfid %>% + # Escribe el dataframe como una hoja de cálculo en formato .csv. No incluyes los nombres de las filas (row.names = FALSE) + # El símbolo "." abajo significa que la función write.csv() va a operar sobre el objeto que proporcionó el pipe, que en este caso es el objeto sim_dats_rfid + write.csv(x = ., file = rfid_file, row.names = FALSE) + +``` + +Como se especifica en la documentación para la función `write.csv()`, esta función va a incluir las nombres de las columnas en la hoja de cálculo por defecto. La función no va a adjuntar esta información del `dataframe` en el archivo de `.csv` si esta hoja de cálculo ya existe, o sea, si ya creaste el archivo de `.csv` y vuelves a correr el código arriba, el archivo se va a sobrescribir por defecto. + +Puedes revisar que `write.csv()` funcionó como esperabas con usar `list.files()` para ver los archivos en tu directorio de trabajo. +```{r eval = FALSE} + +# Ve una lista de todos los archivos en este path +list.files(path) + +``` + +También puedes usar `list.files()` para customizar una búsqueda con el argumento `pattern` (patrón). Usar el argumento `pattern` es parecido a buscar una palabra específica adentro de un documento de texto. El símbolo de "$" después de ".csv" significa que la función debería de buscar todos los archivos que *terminan* en el patrón ".csv". +```{r eval = FALSE} + +# Devuelve sólo archivos que terminan en el patrón ".csv" en este path particular +list.files(path, pattern = ".csv$") + +``` + +

Leer una hoja de cálculo

+ +Ahora puedes leer uno de estos archivos con R usando la función `read.csv()`. En el código abajo, vas a proveer el resultado de `read.csv()` a la función `glimpse()` para revisar la estructura del `dataframe` cuando leíste el archivo. El resultado de este código se imprime a la consola porque no está guardado adentro de un objeto. +```{r eval = FALSE} + +read.csv(file.path(path, "test_file.csv")) %>% + glimpse() + +``` + +Ahora que practicaste usar `write.csv()` y `read.csv()`, puedes eliminar el archivo temporal que creaste proveyendo el objeto `rfid_file` a la función `file.remove()`. +```{r eval = FALSE} + +rfid_file <- file.path(path, "test_file.csv") +rfid_file + +file.remove(rfid_file) + +``` + +

Guardar datos simulados para análisis con ABISSMAL

+ +En el código de abajo, vas a trabajar con una serie de pasos para guardar los datos simulados en el formato y en las ubicaciones esperadas por las funciones de ABISSMAL. Para poder usar estas funciones de ABISSMAL, los datos simulados originales se tienen que guardar en una hoja de cálculo diferente por el tipo de sensor y el día de colección de datos. Estas hojas de cálculo se tienen que guardar adentro de una carpeta por tipo de sensor. ABISSMAL guarda los datos originales de esta misma forma cuando el sistema se usa para colectar datos empíricos de animales. + +

Filtrar un `dataframe`

+ +Vas a practicar cómo usar la función `dplyr::filter()` para filtrar filas de un `dataframe` por día y luego guardar un `dataframe` filtrado con `write.csv()`. Para filtrar un `dataframe`, puedes usar una frase condicional adentro de la función `dplyr::filter()`: +```{r} + +# Provee el dataframe a la función filter() con un "pipe" +sim_dats_rfid %>% + # Filtra el dataframe con seleccionar todas las filas en que la columna de día era igual a uno (el primer día de colección de datos) + dplyr::filter(day == 1) %>% + glimpse() + +# Revisa que este paso de filtrar se hizo correctamente. El único valor dentro de la columna del día debería de ser 1 +sim_dats_rfid %>% + dplyr::filter(day == 1) %>% + pull(day) %>% + unique() + +``` + +Puedes obtener resultados similares cuando inviertes la frase condicional dentro de `dplyr::filter()` para eliminar los días que **no** fueron ni el segundo día ni el tercer día de colección de datos. Abajo combinaste dos frases condicionales usando el símbolo de "&". +```{r} + +sim_dats_rfid %>% + # Filtra el dataframe seleccionando todas las filas en que los valores en la columna de día no son iguales a 2 o 3 + dplyr::filter(day != 2 & day != 3) %>% + glimpse() + +# Usa la función par ver los valores únicos en una columna para verificar que el proceso de filtrar se completo bien +sim_dats_rfid %>% + dplyr::filter(day != 2 & day != 3) %>% + pull(day) %>% + unique() + +``` + +Ahora que practicaste filtrar un `dataframe`, puedes combinar este paso de filtrar con escribir un archivo `.csv` que contiene el `dataframe` filtrado por los datos colectados en un solo día: +```{r eval = FALSE} + +# Crea un directorio nuevo adentro de tu directorio de trabajo para guardar datos +file.path(path, "Data") # Revisa el path nuevo +dir.create(file.path(path, "Data")) # Crea el path nuevo + +# Crea un directorio nuevo adentro del directorio de Data para los datos originales de RFID +file.path(path, "RFID") # Revisa el path nuevo +dir.create(file.path(path, "Data", "RFID")) # Crea el path nuevo + +# Inicializa el nombre del archivo nuevo con el path de tu directorio de trabajo +# Asegúrate de especificar que el archivo se va a guardar adentro de la carpeta nueva "RFID" +rfid_file <- file.path(path, "Data/RFID", "test.csv") +rfid_file + +# Filtra los datos simulados de RFID para sacar el primer día de colección de datos +sim_dats_rfid %>% + dplyr::filter(day == 1) %>% + # Escribe el dataframe filtrado como una hoja de cálculo en formato .csv. No incluyas los nombres para las filas + # Recuerda que el símbolo "." significa que la función va a usar el objeto que proveyó la operación de "pipe", que aquí es el dataframe filtrado para seleccionar solo el primer día de colección de datos + write.csv(x = ., file = rfid_file, row.names = FALSE) + +``` + +Revisa que el archivo de prueba se creó correctamente dentro de la carpeta nueva de RFID, y luego puedes eliminar este archivo. +```{r eval = FALSE} + +list.files(file.path(path, "Data/RFID"), pattern = ".csv$") + +rfid_file <- file.path(path, "Data/RFID", "test.csv") +rfid_file + +file.remove(rfid_file) + +``` + +

Practicar escribir un bucle (loop)

+ +Puedes repetir el código de arriba seis veces (tres veces por sensor) para escribir un `dataframe` por cada día de colección de datos por sensor. Pero es mejor evitar repetir el mismo código varias veces, porque cuando escribes código de esta forma es más difícil mantener archivos organizados de código y también es más fácil introducir errores mientras procesas y analizas datos. Cuando necesitas ejecutar el mismo código varias veces, es mejor escribir un bucle o `loop`. Escribir bucles es una habilidad muy importante y vamos a construir un bucle paso por paso. + +Vas a practicar cómo escribir un bucle con la función `lapply()`. +```{r} + +?lapply + +# Crea un vector de los archivos que quieres guardar +files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv")) + +files +length(files) + +``` + +Ahora puedes empezar a escribir la estructura de un bucle. En el código siguiente, el argumento `X` es el número de veces que se va a ejecutar el bucle. En este caso, `X` es un vector numérico de 1 por el largo del vector `files` y contiene los números uno y dos. Por ende, el bucle va a ejecutar dos veces, y cada valor consecutivo en `X` se va a usar en cada iteración correspondiente del bucle para escribir un archivo a la vez. + +El argumento `FUN` es una función customizada que fue escrita usando la notación `function(x){}`. Todo el código adentro de las llaves curvas (abre y cierre) se ejecutará en cada iteración del bucle. El argumento `x` adentro de `funcion()` es la variable de iteración, o la variable que va a tomar un valor diferente del vector `X` en cada iteración. +```{r} + +# En este bucle la variable de iteración x va a tomar cada valor del vector en el argumento X. Por ejemplo, en la primera iteración del bucle, x va a tomar el valor numérico de 1. En la segunda iteración del bucle, x va a tomar el valor numérico de 2. Para probar esta lógica puedes ejecutar el bucle siguiente, y ver el valor de x que se va a imprimir en cada iteración en la consola +lapply(X = 1:length(files), FUN = function(x){ + + x + +}) + +``` + +Como puedes ver, el resultado de este bucle es un `list` con dos elementos. Cada elemento del `list` está rodeado de dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un tamaño de 1 que contiene el valor de la variable de iteración (1 y 2, respectivamente). + +La variable de iteración, o `x`, no existe como un objeto afuera de la función del bucle. Si imprimes `x` afuera del bucle, no se va a encontrar ese objeto. Si creaste un objeto `x` afuera del bucle, verás los contenidos de este objeto cuando imprimes `x`. O sea, escribir un bucle que usa `x` como la variable de iteración no afectará otras líneas de código que usan un objeto que se llama `x` afuera de la función, y viceversa. + +La variable de iteración de una función puede ser otras letras del alfabeto como `i`, `j`, `y`, `z`, o una combinación de múltiples letras, números, guiones bajos, o puntos, siempre y cuando el nombre de la variable empiece con una letra. + +Una propiedad útil de la variable de iteración es que puedes usarlo para indexar vectores, `dataframes`, u otros objetos que creaste afuera del bucle. Por ejemplo, puedes usar `x` e indexar con corchetes para imprimir el nombre de cada archivo que quieras crear: +```{r} + +lapply(X = 1:length(files), FUN = function(x){ + + files[x] + +}) + +``` + +También puedes modificar el código adentro de la función customizada para guardar cada archivo si metes la expresión `files[x]` dentro de la función `write.csv()` y usas una operación de `piping` para usar un `dataframe` como entrada para `write.csv()`. +```{r eval = FALSE} + +lapply(X = 1:length(files), FUN = function(x){ + + # En cada iteración del bucle, vas a guardar el dataframe `files[x]` en una hoja de cálculo con el nombre de archivo de la iteración actual + sim_dats_rfid %>% + write.csv(file = files[x], row.names = FALSE) + +}) + +``` + +Deberías de ver dos resultados `NULL` en la consola si el bucle se ejecuta correctamente. Cuando revises los contenidos de la carpeta anidada de RFID, vas a poder ver que ambos archivos de `.csv` de prueba se escribieron en esta ubicación: +```{r} + +list.files(file.path(path, "Data/RFID")) + +``` + +Acabas de escribir dos hojas de cálculo usando un bucle, pero escribiste el mismo `dataframe` a cada hoja de cálculo. Para poder escribir un `dataframe` diferente a cada hoja de cálculo, puedes añadir el paso de filtrar el `dataframe` que aprendiste arriba. En el código siguiente, también vas a crear otro objecto de vector que se llama `days` (días) y luego usarás la variable de iteración para filtrar `sim_dats_rfid` y escribir una hoja de cálculo por cada día en `days`. +```{r eval = FALSE} + +days <- c(1, 2, 3) + +lapply(X = 1:length(files), FUN = function(x){ + + sim_dats_rfid %>% + # Filtra el dataframe una día a la vez + dplyr::filter(day == days[x]) %>% + # Escribe el dataframe filtrado por el día actual a una hoja de cálculo para ese día + write.csv(file = files[x], row.names = FALSE) + +}) + +``` + +Puedes borrar estos archivos que creaste de prueba. En el código siguiente, vas a ver otro ejemplo de cómo buscar un patrón de texto mientras buscas evidencia de que los archivos de prueba se crearon (antes de borrarlos). La secuencia de caracteres que usas para el argumento `pattern` empieza con el símbolo de `^`, el cual significa que quieres buscar todos los archivos que *empiezan* con el patrón "test". También estás especificando que quieres devolver la ubicación (`path`) completa de cada archivo usando el argumento `full.names = TRUE`, para que la función `file.remove()` tenga toda la información que necesita para borrar estos archivos de prueba. +```{r eval = FALSE} + +rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) +rem_files + +file.remove(rem_files) + +``` + +

Usa un bucle para guardar la hoja de cálculo de RFID

+ +Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle para escribir una hoja de cálculo por día para el sensor de RFID. +```{r eval = FALSE} + +# Crea un vector de los nombres de los archivos que quieres guardar +files <- c( + "RFID_simulated_Pair-01_2023_08_01.csv", + "RFID_simulated_Pair-01_2023_08_02.csv", + "RFID_simulated_Pair-01_2023_08_03.csv" +) + +# Añade el path para la ubicación o carpeta al nombre de cada archivo +files <- file.path(path, "Data/RFID", files) +files + +# Inicializa un vector de los días para poder escribir una hoja de cálculo por día en cada iteración del bucle +days <- c(1, 2, 3) +days + +# Puedes eliminar los nombres de los argumentos de lapply() porque estás especificando los valores de los argumentos en el orden que la función espera por defecto +invisible(lapply(1:length(files), function(x){ + + sim_dats_rfid %>% + # Filtra el dataframe por el día actual en esta iteración + dplyr::filter(day == days[x]) %>% + # Escribe el dataframe filtrado en la hoja de cálculo correcta para el día actual + write.csv(file = files[x], row.names = FALSE) + +})) + +``` + +En el código de arriba, deberías de poder ver un cambio adicional que hicimos al bucle al rodearlo con la función `invisible()`. Esta función silencia el resultado que se imprime en la consola (que viste cuando ejecutaste trozos de código anteriores), donde el resultado de cada iteración de `lapply()` está rodeado de dos pares de corchetes, y luego un solo par de corchetes. `lapply()` es una función que devuelve un `list`, y cuando la función se ejecuta correctamente pero no hay resultados para imprimir (como cuando creas un archivo físico), la función debería de devolver valores de `NULL` que significan resultados vacíos. Este comportamiento se espera con nuestro uso de `lapply()` porque usamos la función para escribir archivos físicos y no para devolver resultados a la consola. Ya que puedes usar `list.files()` para revisar que `lapply()` se ejecutó bien, usar `invisible()` te ayudará a minimizar la cantidad de texto que tienes que revisar en tu consola. +```{r eval = FALSE} + +# Los archivos nuevos de .csv para cada día de datos de RFID están presentes en el directorio esperado, muy bien +list.files(file.path(path, "Data/RFID")) + +``` + +Ahora puedes eliminar los archivos que acabas de crear, porque vas a trabajar en escribir un bucle anidado que va a escribir automáticamente los datos de cada día para cada tipo de sensor. +```{r eval = FALSE} + +files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") + +# Añade el path para el directorio correcto a los nombres de los archivos que quieres borrar +files <- file.path(path, "Data/RFID", files) +files + +file.remove(files) + +``` + +Si quieres más práctica escribiendo bucles, puedes escribir un bucle para guardar una hoja de cálculo para cada día de colección de datos de los sensores infrarrojo. + +

Escribe un bucle anidado para crear hojas de cálculo

+ +Si quieres minimizar la cantidad de código que escribes para guardar los datos por tipo de sensor y por día, puedes guardar archivos de ambos tipos de sensores (RFID y sensores infrarrojo) en el mismo bucle. Para continuar, deberías de crear otro directorio para los datos de los sensores infrarrojo: +```{r eval = FALSE} + +dir.create(file.path(path, "Data", "IRBB")) + +``` + +Para lograr filtrar y escribir datos para ambos tipos de sensores a través de los días de colección de datos, vas a usar un tipo de objeto en R que se llama un `list`. Vas a usar estos `lists` adentro del bucle anidado: +```{r} + +# Crea un list de los nombres de archivos customizados para guardar datos para cada tipo de sensor por día +files <- list( + c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), + c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") +) + +glimpse(files) + +``` + +Los `lists` son objetos útiles porque son muy flexibles. A diferencia de los vectores, una sola lista puede contener varios diferentes tipos de datos. A diferencia de un `dataframe`, los elementos de una lista no necesitan tener las mismas dimensiones. Los elementos de una lista también pueden ser diferentes tipos de objetos con estructuras diferentes. Por ejemplo, un solo `list` puede contener vectores, `dataframes`, y otros `lists`. El `list` que creaste arriba tiene dos elementos, y cada elemento es un vector de tres secuencias de caracteres que contienen los nombres de archivos que especificaste usando la función `c()`. + +Puedes indexar un `list` de una forma parecida a indexar vectores y `dataframes`, pero usar un par o dos pares de corchetes devuelve resultados diferentes: +```{r} + +# Usar un par de corchetes para filtrar un list devuelve el elemento actual en formato de list +files[1] +glimpse(files[1]) + +# Usar dos pares de corchetes devuelve solo el elemento actual, o sea, elimina la estructura de list para demostrar la estructura original de ese elemento (aquí este elemento es un vector) +files[[1]] +glimpse(files[[1]]) + +``` + +Un `list` también puede tener nombres para sus elementos, y los elementos se pueden acceder por nombre: +```{r} + +# Crea un list de los nombres de los archivos que quieres guardar +files <- list( + `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), + `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") +) + +glimpse(files) + +``` + +Accede los elementos de este `list` por nombre: +```{r} + +# Usar un par de corchetes devuelve el elemento "RFID" como una lista +files["RFID"] + +# Usar el símbolo de dolar "$" o dos pares de corchetes devuelve el elemento "RFID" en su formato original +files$RFID + +files[["RFID"]] + +``` + +Los `lists` son tipos de objetos muy útiles para operaciones con bucles anidados. Por ejemplo, si quieres escribir una hoja de cálculo por tipo de sensor y por día, vas a necesitar 1) un bucle para iterar a través de los tipos de sensores y 2) un bucle para iterar a través de días de colección de datos para cada sensor. Puedes usar listas para crear estructuras anidadas de datos que puedes proveer a un bucle anidado, para asegurar que cada capa del bucle se ejecuta de la forma que esperas. Por ejemplo, el `list` que se llama `files` refleja el bucle anidado que necesitas, porque los archivos están ordenados primero por el tipo de sensor (cada elemento del `list`) y luego por día de colección de datos (cada elemento del vector adentro de cada elemento del `list`): +```{r} + +# Crea un vector de los nombres de los sensores +sensors <- c("RFID", "IRBB") + +sensors + +# Inicializa un list nombrado con los nombres de los archivos que quieres escribir +files <- list( + `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), + `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") +) + +files + +# Crea un list de los paths para los archivos de cada sensor. Usarás estos paths dentro de los bucles +file_dirs <- list( + `RFID` = file.path(path, "Data/RFID"), + `IRBB` = file.path(path, "Data/IRBB") +) + +file_dirs + +# Crea un list de los días de colección de datos para cada tipo de sensor +# Esto puede ser un solo vector en vez de una lista porque quieres guardar el mismo número de días por sensor, pero un list es útil por si quieres cambiar los días mismos o el número de días que quieres guardar por sensor +days <- list( + `RFID` = c(1, 2, 3), + `IRBB` = c(1, 2, 3) +) + +days + +# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes cómo filtrar dataframes por día, ese código puede ir adentro de los bucles para minimizar la cantidad de código que escribes. Aquí vas a especificar el dataframe que usarás en las operaciones de filtro para cada tipo de sensor +dats <- list( + `RFID` = sim_dats_rfid, + `IRBB` = sim_dats_irbb +) + +glimpse(dats) + +``` + +Cuando hayas establecido las estructuras de los datos para informar la operación del bucle, puedes escribir el bucle anidado mismo. Este bucle anidado es una estructura compleja, y por ende es útil probar el bucle con valores determinados de cada variable de iteración (`x` y `y` abajo). + +Después de escribir este bucle pero antes de ejecutar la estructura completa del bucle, deberías de probar el código adentro de cada capa del bucle. Para lograr esto, puedes inicializar los valores de las variables de iteración y luego correr el código adentro de cada bucle (pero sin ejecutar el bucle entero). Esta forma de revisar el bucle es equivalente a congelar el bucle en el tiempo, para que puedas ver el resultado del código para una sola iteración (abajo vas a ver la primera iteración para cada capa del bucle cuando ambos `x` y `y` tiene el valor numérico de uno). + +Para lograr este tipo de chequeo, deberías de ejecutar el código para "congelar" las variables de iteración en la primera iteración de cada bucle ( o sea, inicializar `x` y `y` con el valor de uno). Luego deberías de ejecutar el código dentro de cada bucle, empezando con la creación de `days_tmp`, luego las operaciones de indexar y filtrar el `dataframe`, y luego filtrar los nombres de los archivos. No deberías de ejecutar las líneas con `lapply()` porque quieres evitar ejecutar los bucles completos hasta que estés segura que el código adentro de cada bucle funciona de la forma que esperas. + +*Nota importante*: Arriba aprendiste que las variables de iteración no existen afuera de un bucle. Esta forma de probar el código adentro del bucle, en que no estás ejecutando los bucles mismos, es equivalente a probar el código afuera del bucle y por ende, los valores de las variables de iteración que inicialices afuera del bucle se van a respetar. + +Si observas el código dentro de cada bucle, deberías de ver que entre el bucle exterior y el bucle interior, vas a usar el nombre del sensor para la primera iteración del bucle exterior ("RFID") para determinar los días para las iteraciones del bucle interior (en `days_tmp`). Adentro del bucle interior, vas a filtrar el `list` de `dataframes` por sensor, y luego por los días que quieres por sensor. Luego vas a indexar el nombre del archivo para el tipo de sensor actual y el día actual con una combinación de indexar la lista de nombres de los archivos con uno o dos pares de corchetes. Abajo, las líneas que abren y cierran los bucles mismos están comentados para guiar tu chequeo (o sea para guiar cuales líneas de código deberías de ejecutar): +```{r eval = FALSE} + +# Congela las variables de iteración para el chequeo +x <- 1 +y <- 1 + +# El bucle exterior: empieza con iterar a través de los sensores +# invisible(lapply(1:length(sensores), function(x){ + + # Para obtener los días correctos para el tipo de sensor actual, deberías de indexar el list nombrado de días + # Este paso de indexar es importante para que el bucle interior ejecute correctamente + sensors[x] # Una secuencia de caracteres con el nombre del sensor + + # Coloca la secuencia de caracteres con el nombre del sensor adentro de dos pares de corchetes para extraer el vector de días para el tipo de sensor actual + days_tmp <- days[[sensors[x]]] + + days_tmp + + # El bucle interior: itera a través de días para cada tipo de sensor + # lapply(1:length(days_tmp), function(y){ + + # Para obtener el dataframe del tipo de sensor actual, puedes usar "x" adentro de dos pares de corchetes para extraer el dataframe del list + dats[[x]] %>% + # Para filtrar el dataframe por el día actual puedes usar "y" para indexar el vector temporal de días (para extraer un solo elemento de este vector) + dplyr::filter(day == days_tmp[y]) %>% + glimpse() + + # Usa dos pares de corchetes para acceder el vector de los nombres de los archivos para el tipo de sensor actual que está adentro de la lista. Luego usa la variable "y" para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteración + files[[x]] + + files[[x]][y] + + # También vas a combinar el nombre del archivo con el path correcto: + file.path(file_dirs[[x]], files[[x]][y]) + + # }) + +# })) + +``` + +Ahora deberías de tener una mejor idea sobre cómo cada bucle opera a través de diferentes estructuras de datos para realizar la tarea que quieres (en este caso, escribir una hoja de cálculo por tipo de sensor y por día). Más adelante puedes modificar la estructura entera de los bucles para reemplazar las líneas que escribiste y realizar el chequeo con las operaciones finales que quieres realizar: +```{r eval = FALSE} + +# El bucle exterior: empieza con iterar a través de los sensores +invisible(lapply(1:length(sensors), function(x){ + + # Para obtener los días correctos para el tipo de sensor actual, deberías de indexar el list nombrado de días + # Este paso de indexar es importante para que el bucle interior se ejecute correctamente + days_tmp <- days[[sensors[x]]] + + # El bucle interior: itera a través de días para cada tipo de sensor para escribir una hoja de cálculo por tipo de sensor y día + lapply(1:length(days_tmp), function(y){ + + # Usa la variable "x" para acceder el dataframe para el tipo de sensor actual y luego usa la variable "y" para filtrar este dataframe por el día actual + dats[[x]] %>% + # Filtra el dataframe por el día actual + dplyr::filter(day == days_tmp[y]) %>% + # Usa una operación de indexar con doble corchetes para especificar el path correcto por tipo de sensor + # Luego usa operaciones de filtrar con doble corchetes y un par de corchetes para acceder el nombre correcto del archivo para el tipo de sensor y el día actual + write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) + + }) + +})) + +``` + +Esta estructura de bucle debería de haber creado un archivo por tipo de sensor y día en el directorio correcto por tipo de sensor. Puedes revisar que estos seis archivos existen adentro del directorio para cada tipo de sensor en tu directorio de trabajo: +```{r eval = FALSE} + +list.files(file.path(path, "Data/RFID")) + +list.files(file.path(path, "Data/IRBB")) + +``` + +En este tutorial aprendiste más sobre cómo filtrar `dataframes` y guardar estos objetos como hojas de cálculo, y cómo usar bucles (loops) de una capa y estructuras de bucles anidados. En el siguiente tutorial vas a usar las hojas de cálculo de los datos simulados de RFID y sensores infrarrojo que creaste para empezar a procesar y analizar datos con las funciones de ABISSMAL. \ No newline at end of file diff --git a/R/vignettes/Tutorial_04_GuardarDatos.html b/R/vignettes/Tutorial_04_GuardarDatos.html new file mode 100644 index 0000000..19d6af1 --- /dev/null +++ b/R/vignettes/Tutorial_04_GuardarDatos.html @@ -0,0 +1,2526 @@ + + + + + + + + + + + + + + + +Tutorial 04: Guardar Datos + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, +siguiendo las convenciones +de traducción de los Carpentries, incluyendo usar el género femenino +por defecto. Si encuentras errores de ortografía que impiden tu +habilidad de completar los tutoriales, por favor reporta los errores de +ortografía a GitHub usando los pasos en el primer tutorial para reportar +un “Issue”.

+

+Resumen del tutorial y objetivos de aprendizaje +

+

En este cuarto tutorial, vas a guardar hojas de cálculo de las +detecciones simuladas de movimientos de animales a tu computadora. Vas a +continuar usando habilidades que aprendiste en los tutoriales +anteriores, y vas a aprender nuevas habilidades que incluyen:

+
    +
  1. Indexar y filtrar dataframes
  2. +
  3. Revisar la estructura de dataframes con R base y el +tidyverse
  4. +
  5. Guardar objetos de R como archivos físicos en tu computadora
  6. +
  7. Leer archivos de tu computadora a R
  8. +
  9. Escribir y probar bucles
  10. +
+

+Cargar paquetes e inicializar el path de tu directorio de +trabajo +

+
rm(list = ls()) # Limpia tu ambiente global
+
+library(tidyverse) # Carga la colección de paquetes del tidyverse
+
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo
+

+Crear los datos simulados +

+

En el código abajo, vas a recrear los datos simulados de RFID y +sensores infrarrojo del tutorial anterior. Aquí estamos combinando el +código en menos trozos comparado con el tercer tutorial:

+
# Crea un vector de cuatro marcas de tiempo de RFID en formato HH:MM:SS
+rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00")
+
+# Añade eventos de posar a los datos de RFID
+rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05")
+
+glimpse(rfid_ts)
+
##  chr [1:14] "10:00:00" "10:05:00" "11:00:00" "11:05:00" "08:00:00" ...
+

Aquí “IRBB” significa “infrared beam breakers” o sensores de +infrarrojo.

+
# Simula marcas de tiempo para los pares externos ("o_") e internos ("i_") de sensores infrarrojo para una entrada y una salida, y luego otra entrada y salida
+o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
+i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59")
+
+# Simula unos errores de detección por la antena de RFID en las marcas de tiempo de cada par de sensores infrarrojo
+# Estos errores de detección surgieron en cuatro movimientos adicionales: dos entradas y dos salidas
+o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
+i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24")
+
+# Simula unas detecciones de ruido en las marcas de tiempo del par externo de sensores infrarrojo
+o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11")
+
+glimpse(o_irbb_ts)
+
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+
glimpse(i_irbb_ts)
+
##  chr [1:8] "10:00:01" "10:04:59" "11:00:01" "11:04:59" "06:05:04" ...
+

+Simula tres días de colección de datos de RFID +

+

En el código abajo, vas a combinar el vector de marcas de tiempo de +RFID que inicializaste arriba con metadatos en un +dataframe. Estos metadatos van a incluir el año, el mes, y +el día, y también una columna con los valores de dos etiquetas PIT (una +etiqueta por individuo simulado), y una columna con información sobre el +tipo de sensor. Deberías de reconocer partes de este código desde el +tutorial anterior:

+
# Crea un vector para la réplica experimental
+exp_rep <- rep(x = "Nest_01", times = length(rfid_ts))
+
+# Crea un vector de las identidades de las etiquetas PIT
+# Asigna las primeras cuatro detecciones de RFID al primer individuo, el primer evento de posar (cuatro detecciones) al primer individuo, y el segundo evento de posar (seis detecciones) al segundo individuo
+# Estas tres expresiones de rep() están combinadas en un solo vector usando la función c()
+PIT_tag <- c(rep("1357aabbcc", 4), rep("1357aabbcc", 4), rep("2468zzyyxx", 6))
+
+# Crea el dataframe con los metadatos de réplica experimental y las marcas de tiempo
+sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts)
+
+# Sobrescribe el dataframe con la versión modificada que tiene columnas para el año, el mes, y el día
+sim_dats_rfid <- sim_dats_rfid %>%
+  dplyr::mutate(
+    year = 2023
+  ) %>%
+  dplyr::mutate(
+    month = 08,
+    day = 01
+  ) %>%
+  # Añade los metadatos de las etiquetas PIT en una columna nueva
+  dplyr::mutate(
+    PIT_tag = PIT_tag
+  ) %>% 
+  dplyr::mutate(
+    sensor_id = "RFID"
+  )
+
+glimpse(sim_dats_rfid)
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

Ahora puedes usar este dataframe para simular el proceso +de colección de datos a través de dos días más. Para crear más +observaciones (filas) para dos días adicionales, puedes adjuntar filas +de una copia modificada de sim_dats_rfid al objeto original +de sim_dats_rfid.

+

En el código abajo, estás usando una operación pipe para +usar sim_dats_rfid como entrada en +bind_rows(), y con esta notación estás especificando que +sim_dats_rfid es el objeto original al cual quieres +adjuntar más filas. Luego el código adentro de bind_rows() +especifica el dataframe, o las filas nuevas, que quieres +adjuntar a sim_dats_rfid. En este case, el código adentro +de bind_rows() provee sim_dats_rfid a +dplyr::mutate() para modificar la columna de +day y representar un día adicional de colección de datos. +Luego repites este proceso para añadir un tercer día de recolectar +datos:

+
sim_dats_rfid <- sim_dats_rfid %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  ) %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 03
+      )
+  )
+
+glimpse(sim_dats_rfid) # Tres veces el número original de filas, se ve bien
+
## Rows: 42
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,…
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

+Revisar columnas de dataframe con R base +

+

Acabas de revisar la estructura del dataframe para +confirmar que los datos simulados tienen datos recolectados a través de +tres días. También puedes revisar los valores únicos que están presentes +en la columna de day. Abajo puedes ver una forma de revisar +los valores únicos adentro de una columna de un dataframe, +usando dos ejemplos diferentes de notación de R base para acceder +columnas adentro de un dataframe:

+
# Escribir una expresión con el nombre de un objeto dataframe, un símbolo $, y el nombre de una columna te ayuda sacar o acceder una columna a la vez de un dataframe. Una columna de un dataframe es un vector, por ende cuando ejecutas este código deberías de ver un vector de valores impreso en la consola
+sim_dats_rfid$day
+
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
+## [39] 3 3 3 3
+
# Puedes también acceder una columna en un dataframe con indexar si escribes dos pares corchetes (ambos pares de abrir y cerrar) después del nombre del dataframe, y colocas el nombre de la columna entrecomillas adentro del par interno de corchetes
+sim_dats_rfid[["day"]]
+
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
+## [39] 3 3 3 3
+
# Puedes usar la función unique() para ver los valores únicos adentro de un vector, incluyendo una columna en un dataframe
+unique(sim_dats_rfid$day) # Tres días, se ve bien
+
## [1] 1 2 3
+
unique(sim_dats_rfid[["day"]]) # Tres días, se ve bien
+
## [1] 1 2 3
+

+Revisar columnas de un dataframe con el +tidyverse +

+

También puedes revisar valores únicos en una columna usando funciones +del tidyverse. En la expresión abajo, vas a usar una +expresión de pipe para proveer el dataframe +sim_dats_rfid a la función pull(), que va a +facilitar acceder la columna day del dataframe +como un vector. Luego este vector de la columna day se va a +usar como entrada a la función unique() para revisar los +valores únicos del vector mismo. La función unique() no +requiere un argumento adentro de los paréntesis porque ya recibió el +valor de entrada que necesita a través de la operación de +piping.

+
# Tres días, se ve bien
+sim_dats_rfid %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2 3
+

+Simula tres días de recolectar datos de los sensores infrarrojo +

+

Ahora puedes repetir este proceso de crear un dataframe +con metadatos para los datos de los sensores de infrarrojo. Dado que los +sensores de infrarrojo no colectan información sobre la identidad única +de individuos, vas a añadir columnas para el año, el mes, el día, y el +tipo de sensor. También vas a simular la colección de datos para estos +sensores a través de los mismos tres días que los datos simulados de +RFID.

+
# Sobrescribe el vector exp_rep con un vector nuevo que tiene el mismo largo que los vectores o_irbb_ts y i_irbb_ts juntos
+exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts))
+
+# Añade las marcas de tiempo de ambos pares de sensores infrarrojo a la misma columna usando c()
+sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = c(o_irbb_ts, i_irbb_ts))
+
+sim_dats_irbb <- sim_dats_irbb %>%
+  dplyr::mutate(
+    year = 2023,
+    month = 08,
+    day = 01,
+    # Añade un identificador único para cada par de sensores
+    # Cada etiqueta única se repetirá por lo largo del vector de marcas de tiempo de cada par de sensores infrarrojo
+    sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts)))
+  )
+
+glimpse(sim_dats_irbb)
+
## Rows: 27
+## Columns: 6
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "09:59:59", "10:05:01", "10:59:59", "11:05:01", "06:05:05",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
+## $ sensor_id  <chr> "Outer Beam Breakers", "Outer Beam Breakers", "Outer Beam B…
+
sim_dats_irbb <- sim_dats_irbb %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  ) %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 03
+      )
+  )
+
+glimpse(sim_dats_irbb) # Tres veces el número de filas, se ve bien
+
## Rows: 81
+## Columns: 6
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "09:59:59", "10:05:01", "10:59:59", "11:05:01", "06:05:05",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
+## $ sensor_id  <chr> "Outer Beam Breakers", "Outer Beam Breakers", "Outer Beam B…
+
# Tres días, se ve bien
+sim_dats_irbb %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2 3
+

+Guarda un dataframe como un archivo físico +

+

Los dataframes que creas y manipulas en R se pueden +guardar como archivos físicos en tu directorio de trabajo. Tienes muchas +opciones diferentes para guardar dataframes, pero +recomiendo que uses formato .csv porque este formato es +compatible con R, Microsoft Word, y otros programas. Puedes usar la +función write.csv() para guardar dataframes a +hojas de cálculo .csv en tu computadora:

+
?write.csv
+

Para escribir un archivo físico a tu directorio de trabajo, necesitas +comunicarle a R 1) donde guardar el archivo y 2) el nombre del archivo +que quieres crear. Puedes pasarle ambas piezas de información a +write.csv() con combinar tu directorio de trabajo y el +nombre del archivo usando la función file.path(). Para este +ejemplo, vas a crear un archivo de prueba mientras practicas cómo usar +write.csv():

+
# Combina el path para tu directorio de trabajo con el nombre del archivo que quieres escribir
+# La función file.path() combinara ambas piezas de información en un solo path para este archivo
+rfid_file <- file.path(path, "test_file.csv")
+
+# Este objeto contiene la ubicación dónde vas a guardar el archivo, y luego el nombre del archivo
+rfid_file
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/test_file.csv"
+

Luego puedes proveer el dataframe a +write.csv() usando un pipe y puedes +especificar información adicional para crear el archivo +.csv, como si quieres añadir una columna adicional de +identidades numéricas de las filas:

+
sim_dats_rfid %>%
+  # Escribe el dataframe como una hoja de cálculo en formato .csv. No incluyes los nombres de las filas (row.names = FALSE)
+  # El símbolo "." abajo significa que la función write.csv() va a operar sobre el objeto que proveyó el pipe, que en este caso es el objeto sim_dats_rfid
+  write.csv(x = ., file = rfid_file, row.names = FALSE)
+

Como se especifica en la documentación para la función +write.csv(), esta función va a incluir las nombres de las +columnas en la hoja de cálculo por defecto. La función también no va a +adjuntar esta información en el dataframe al archivo de +.csv si esta hoja de cálculo ya existe, o sea, si ya +creaste el archivo de .csv y vuelves a correr el código +arriba, el archivo se va a sobrescribir por defecto.

+

Puedes revisar que write.csv() funcionó como esperabas +con usar list.files() para ver los archivos en tu +directorio de trabajo.

+
# Ve una lista de todos los archivos en este path
+list.files(path)
+

También puedes usar list.files() para customizar una +búsqueda con el argumento pattern. Usar el argumento +pattern es parecido a buscar una palabra específica adentro +de un documento de texto. El símbolo de “$” después de “.csv” significa +que la función debería de buscar todos los archivos que +terminan en el patrón “.csv”.

+
# Devuelve sólo archivos que terminan en el patrón ".csv" en este path particular
+list.files(path, pattern = ".csv$")
+

+Leer una hoja de cálculo +

+

Ahora puedes leer uno de estos archivos con R usando la función +read.csv(). En el código abajo, vas a proveer el resultado +de read.csv() a la función glimpse() para +revisar la estructura del dataframe creado en R cuando +leíste el archivo. El resultado de este código se imprime a la consola +porque no está guardado adentro de un objeto.

+
read.csv(file.path(path, "test_file.csv")) %>% 
+  glimpse()
+

Ahora que practicaste usar write.csv() y +read.csv(), puedes eliminar el archivo temporal que creaste +con proveer el objeto rfid_file a la función +file.remove().

+
rfid_file <- file.path(path, "test_file.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Guardar datos simulados para análisis con ABISSMAL +

+

En el código abajo, vas a trabajar con una serie de pasos para +guardar los datos simulados en el formato y en las ubicaciones esperadas +por las funciones de ABISSMAL. Para poder usar estas funciones de +ABISSMAL, los datos simulados originales se tienen que guardar en una +hoja de cálculo diferente por el tipo de sensor y el día de colección de +datos. Estas hojas de cálculo se tienen que guardar adentro de una +carpeta por tipo de sensor. ABISSMAL guarda los datos originales de esta +misma forma cuando el sistema se usa para colectar datos empíricos de +animales.

+

+Filtrar un dataframe +

+

Vas a practicar cómo usar la función dplyr::filter() +para filtrar filas de un dataframe por día y luego guardar +un dataframe filtrado con write.csv(). Para +filtrar un dataframe, puedes usar una frase condicional +adentro de la función dplyr::filter():

+
# Provee el dataframe a la función filter() con un "pipe"
+sim_dats_rfid %>% 
+  # Filtra el dataframe con seleccionar todas las filas en que la columna de día era igual a uno (el primer día de colección de datos)
+  dplyr::filter(day == 1) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+
# Revisa que este paso de filtrar se hizo correctamente. El único valor adentro de la columna del día debería de ser uno, y se ve bien
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1
+

Puedes obtener resultados similares cuando inviertes la frase +condicional adentro de dplyr::filter() para eliminar los +días que no fueron ni el segundo día ni el tercer día +de colección de datos. Abajo combinaste dos frases condicionales usando +el símbolo de “&”.

+
sim_dats_rfid %>%
+  # Filtra el dataframe con seleccionar todas las filas en que los valores en la columna de día no son iguales a 2 o 3
+  dplyr::filter(day != 2 & day != 3) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+
# Usa la función par ver los valores únicos en una columna para revisar que el proceso de filtrar se completo bien. Se ve bien
+sim_dats_rfid %>% 
+  dplyr::filter(day != 2 & day != 3) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1
+

Ahora que practicaste filtrar un dataframe, puedes +combinar este paso de filtrar con escribir un archivo .csv +que contiene el dataframe filtrado por los datos colectados +en un solo día:

+
# Crea un directorio nuevo adentro de tu directorio de trabajo para guardar datos
+file.path(path, "Data") # Revisa el path nuevo
+dir.create(file.path(path, "Data")) # Crea el path nuevo
+
+# Crea un directorio nuevo adentro del directorio de Data para los datos originales de RFID
+file.path(path, "RFID") # Revisa el path nuevo
+dir.create(file.path(path, "Data", "RFID")) # Crea el path nuevo
+
+# Inicializa el nombre del archivo nuevo con el path de tu directorio de trabajo 
+# Asegúrate de especificar que el archivo se va a guardar adentro de la carpeta nueva "RFID"
+rfid_file <- file.path(path, "Data/RFID", "test.csv")
+rfid_file
+
+# Filtra los datos simulados de RFID para sacar el primer día de colección de datos
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>% 
+  # Escribe el dataframe filtrado como una hoja de cálculo en formato .csv. No incluyes los nombres para las filas
+  # Recuerda que el símbolo "." significa que la función va a usar el objeto que proveyó la operación de "pipe", que aquí es el dataframe filtrado para seleccionar solo el primer día de colección de datos
+  write.csv(x = ., file = rfid_file, row.names = FALSE)
+

Revisa que el archivo de prueba se creó adentro de la carpeta nueva +de RFID, y luego puedes eliminar este archivo.

+
list.files(file.path(path, "Data/RFID"), pattern = ".csv$")
+
+rfid_file <- file.path(path, "Data/RFID", "test.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Practicar escribir un bucle +

+

Podrías repetir el código arriba seis veces (tres veces por sensor) +para escribir un dataframe por cada día de colección de +datos por sensor. Pero es mejor evitar repetir el mismo código varias +veces, porque cuando escribes código de esta forma es más difícil +mantener archivos organizados de código y también es más fácil +introducir errores mientras procesas y analizas datos. Cuando necesitas +ejecutar el mismo código varias veces, es mejor escribir un bucle. +Escribir bucles es una habilidad muy importante y vamos a construir un +bucle paso por paso.

+

Vas a practicar cómo escribir un bucle con la función +lapply().

+
?lapply
+
+# Crea un vector de los archivos que quieres guardar
+files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv"))
+
+files
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test1.csv"
+## [2] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test2.csv"
+
length(files)
+
## [1] 2
+

Ahora puedes empezar a escribir la estructura de un bucle. En el +código abajo, el argumento X es el número de veces que se +va a ejecutar el bucle. En este caso, X es un vector +numérico de uno al largo del vector files y contiene los +números uno y dos. Por ende, el bucle va a ejecutar dos veces, y cada +valor consecutivo en X se va a usar en cada iteración +correspondiente del bucle para escribir un archivo a la vez.

+

El argumento FUN es una función customizada que fue +escrita usando la notación function(x){}. Todo el código +adentro de las llaves curvas (abre y cierre) se ejecutará en cada +iteración del bucle. El argumento x adentro de +funcion() es la variable de iteración, o la variable que va +a tomar un valor diferente del vector X en cada +iteración.

+
# En este bucle la variable de iteración x va a tomar cada valor del vector en el argumento X. Por ejemplo, en la primera iteración del bucle, x va a tomar el valor numérico de 1. En la segunda iteración del bucle, x va a tomar el valor numérico de 2. Para probar esta lógica puedes ejecutar el bucle abajo, y ver el valor de x que se va a imprimir en cada iteración en la consola
+lapply(X = 1:length(files), FUN = function(x){
+  
+  x
+  
+})
+
## [[1]]
+## [1] 1
+## 
+## [[2]]
+## [1] 2
+

Como puedes ver, el resultado de este bucle es un list +con dos elementos. Cada elemento del list está rodeado de +dos pares de corchetes ([[1]] y [[2]]) y contiene un vector con un largo +de uno que contiene el valor dla variable de iteración (uno y dos, +respectivamente).

+

La variable de iteración, o x, no existe como un objeto +afuera de la función del bucle. Si imprimes x afuera del +bucle, no se va a encontrar ese objeto. Si creaste un objeto +x afuera del bucle arria, verás los contenidos de este +objeto cuando imprimes x. O sea, escribir un bucle que usa +x como la variable de iteración no afectará otras líneas de +código que usan un objeto que se llama x afuera de la +función, y viceversa.

+

La variable de iteración de una función puede ser otras letras del +alfabeto como i, j, y, +z, o una combinación de múltiples letras, números, guiones +bajo, o periodos, siempre y cuando el nombre de la variable empieza con +una letra.

+

Una propiedad útil dla variable de iteración es que puedes usarlo +para indexar vectores, dataframes, u otros objetos que +creaste afuera del bucle. Por ejemplo, puedes usar x e +indexar con corchetes para imprimir el nombre de cada archivo que +quieres crear:

+
lapply(X = 1:length(files), FUN = function(x){
+  
+  files[x]
+  
+})
+
## [[1]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test1.csv"
+## 
+## [[2]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test2.csv"
+

También puedes modificar el código adentro de la función customizada +para guardar cada archivo si metes la expresión files[x] +adentro de la función write.csv() y usas una operación de +piping para usar un dataframe como entrada +para write.csv().

+
lapply(X = 1:length(files), FUN = function(x){
+  
+  # En cada iteración del bucle, vas a guardar el dataframe `files[x]` en una hoja de cálculo con el nombre de archivo de la iteración actual
+  sim_dats_rfid %>%
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

Deberías de ver dos resultados NULL en la consola si el +bucle se ejecuta correctamente. Cuando revisas los contenidos de la +carpeta anidada de RFID, vas a poder ver que ambos archivos de +.csv de prueba se escribieron a esta ubicación:

+
list.files(file.path(path, "Data/RFID"))
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+

Acabas de escribir dos hojas de cálculo usando un bucle, pero +escribiste el mismo dataframe a cada hoja de cálculo. Para +poder escribir un dataframe diferente a cada hoja de +cálculo, puedes añadir el paso de filtrar el dataframe que +aprendiste arriba. En el código abajo, también vas a crear otro objecto +de vector que se llama days (días) y luego usarás el +variable de iteración para filtrar sim_dats_rfid y escribir +una hoja de cálculo por cada día en days.

+
days <- c(1, 2, 3)
+
+lapply(X = 1:length(files), FUN = function(x){
+  
+  sim_dats_rfid %>%
+    # Filtra el dataframe una día a la vez
+    dplyr::filter(day == days[x]) %>%
+    # Escribe el dataframe filtrado por el día actual a una hoja de cálculo para ese día 
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

Puedes borrar estos archivos que creaste de prueba. En el código +abajo, vas a ver otro ejemplo de buscar un patrón de texto mientras +buscas evidencia que los archivos de prueba se crearon (antes de +borrarlos). La secuencia de caracteres que usas para el argumento +pattern empieza con el símbolo de ^, el cual +significa que quieres buscar todos los archivos que empiezan +con el patrón “test”. También estas especificando que quieres devolver +la ubicación (path) completa de cada archivo usando el +argumento full.names = TRUE, para que la función +file.remove() tenga toda la información que necesita para +borrar estos archivos de prueba.

+
rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE)
+rem_files
+
+file.remove(rem_files)
+

+Usa un bucle para guardar la hoja de cálculo de RFID +

+

Ahora puedes juntar todo lo que aprendiste arriba y usar el bucle +para escribir una hoja de cálculo por día para el sensor de RFID.

+
# Crea un vector de los nombres de los archivos que quieres guardar
+files <- c(
+  "RFID_simulated_Pair-01_2023_08_01.csv", 
+  "RFID_simulated_Pair-01_2023_08_02.csv", 
+  "RFID_simulated_Pair-01_2023_08_03.csv"
+)
+
+# Añade el path para la ubicación o carpeta al nombre de cada archivo
+files <- file.path(path, "Data/RFID", files) 
+files
+
+# Inicializa un vector de los días para poder escribir una hoja de cálculo por día en cada iteración del bucle
+days <- c(1, 2, 3)
+days
+
+# Puedes eliminar los nombres de los argumentos de lapply() porque estás especificando los valores de los argumentos en el orden que la función espera por defecto
+invisible(lapply(1:length(files), function(x){
+  
+  sim_dats_rfid %>%
+    # Filtra el dataframe por el día actual en esta iteración
+    dplyr::filter(day == days[x]) %>%
+    # Escribe el dataframe filtrado a la hoja de cálculo correcta para el día actual
+    write.csv(file = files[x], row.names = FALSE)
+  
+}))
+

En el código arriba, deberías de poder ver un cambio adicional que +hicimos al bucle con rodearlo con la función invisible(). +Esta función silencia el resultado que se imprima a la consola (que +viste cuando ejecutaste trozos de código arriba), en que el resultado de +cada iteración de lapply() está rodeado de dos pares de +corchetes, y luego un solo par de corchetes. lapply() es +una función que devuelve un list, y cuando la función se +ejecuta correctamente pero no hay resultados para imprimir (como cuando +creas un archivo físico), la función debería de devolver valores de +NULL que significan resultados vacíos. Este comportamiento +se espera con nuestro uso de lapply() porque usamos la +función para escribir archivos físicos y no para devolver resultados a +la consola. Ya que puedes usar list.files() para revisar +que lapply() se ejecuto bien, usar invisible() +te ayudará minimizar la cantidad de texto que tienes que revisar en tu +consola.

+
# Los archivos nuevos de .csv para cada día de datos de RFID están presentes en el directorio esperado, se ve bien
+list.files(file.path(path, "Data/RFID"))
+

Ahora puedes eliminar los archivos que acabas de escribir, porque vas +a trabajar en escribir un bucle anidado que va a automáticamente +escribir los datos de cada día para cada tipo de sensor.

+
files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv")
+
+# Añade el path para el directorio correcto a los nombres de los archivos que quieres borrar
+files <- file.path(path, "Data/RFID", files) 
+files
+
+file.remove(files)
+

Si quieres más práctica escribiendo bucles, puedes escribir un bucle +para guardar una hoja de cálculo para cada día de colección de datos +para los sensores infrarrojo.

+

+Escribe un bucle anidado para crear hojas de cálculo +

+

Si quieres minimizar la cantidad de código que escribes para guardar +los datos por tipo de sensor y por día, puedes guardar archivos de ambos +tipos de sensores (RFID y sensores infrarrojo) en el mismo bucle. Para +continuar, deberías de crear otro directorio para los datos de los +sensores infrarrojo:

+
dir.create(file.path(path, "Data", "IRBB"))
+

Para lograr filtrar y escribir datos para ambos tipos de sensores a +través de los días de colección de datos, vas a usar un tipo de objeto +en R que se llama un list. Vas a usar estos +lists adentro del bucle anidado:

+
# Crea un list de los nombres de archivos customizados para guardar datos para cada tipo de sensor y día
+files <- list(
+  c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ : chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+##  $ : chr [1:3] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.csv" "IRBB_simulated_Pair-01_2023_08_03.csv"
+

Los lists son objetos útiles porque son muy flexibles. A +diferencia de vectores, una sola lista puede contener varios diferentes +tipos de datos. A diferencia de un dataframe, los elementos +de una lista no necesitan tener las mismas dimensiones. Los elementos de +una lista también pueden ser diferentes tipos de objetos con estructuras +diferentes. Por ejemplo, un solo list puede contener +vectores, dataframes, y otros lists. El +list que creaste arriba tiene dos elementos, y cada +elemento es un vector de tres secuencias de caracteres que contienen los +nombres de archivos que especificaste usando la función +c().

+

Puedes indexar un list de una forma parecida a indexar +vectores y dataframes, pero usar un par o dos pares de +corchetes devuelve resultados diferentes:

+
# Usar un par de corchetes para filtrar un list devuelve el elemento actual en formato de list
+files[1]
+
## [[1]]
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
glimpse(files[1])
+
## List of 1
+##  $ : chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+
# Usar dos pares de corchetes devuelve solo el elemento actual, o sea, elimina la estructura de list para demostrar el estructura original de ese elemento (aquí este elemento es un vector)
+files[[1]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
glimpse(files[[1]])
+
##  chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" ...
+

Un list también puede tener nombres para sus elementos, +y los elementos se pueden acceder por nombre:

+
# Crea un list de los nombres de los archivos que quieres guardar
+files <- list(
+  `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ RFID: chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+##  $ IRBB: chr [1:3] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.csv" "IRBB_simulated_Pair-01_2023_08_03.csv"
+

Accede los elementos de este list por nombre:

+
# Usar un par de corchetes devuelve el elemento "RFID" como una lista
+files["RFID"]
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
# Usar el símbolo de dolar "$" o dos pares de corchetes devuelve el elemento "RFID" en su formato original
+files$RFID
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
files[["RFID"]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+

Los lists son tipos de objetos muy útiles para +operaciones con bucles anidados. Por ejemplo, si quieres escribir una +hoja de cálculo por tipo de sensor y por día, vas a necesitar 1) un +bucle para iterar a través de los tipos de sensores y 2) un bucle para +iterar a través de días de colección de datos para cada sensor. Puedes +usar listas para crear estructuras anidadas de datos que puedes proveer +a un bucle anidado, para asegurar que cada capa del bucle ejecuta de la +forma que esperas. Por ejemplo, el list que se llama +files refleja el bucle anidado que necesitas, porque los +archivos están ordenados primero por el tipo de sensor (cada elemento +del list) y luego por día de colección de datos (cada +elemento del vector adentro de cada elemento del list):

+
# Crea un vector de los nombres de los sensores
+sensors <- c("RFID", "IRBB")
+
+sensors
+
## [1] "RFID" "IRBB"
+
# Inicializa un list nombrado con los nombres de los archivos que quieres escribir
+files <- list(
+  `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+files
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+## 
+## $IRBB
+## [1] "IRBB_simulated_Pair-01_2023_08_01.csv"
+## [2] "IRBB_simulated_Pair-01_2023_08_02.csv"
+## [3] "IRBB_simulated_Pair-01_2023_08_03.csv"
+
# Crea un list de los paths para los archivos de cada sensor. Usarás estos paths adentro de los bucles
+file_dirs <- list(
+  `RFID` = file.path(path, "Data/RFID"),
+  `IRBB` = file.path(path, "Data/IRBB") 
+)
+
+file_dirs
+
## $RFID
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID"
+## 
+## $IRBB
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/IRBB"
+
# Crea un list de los días de colección de datos para cada tipo de sensor
+# Esto puede ser un solo vector en vez de una lista porque quieres guardar el mismo número de días por sensor, pero un list es útil por si quieres cambiar los días mismos o el número de días que quieres guardar por sensor
+days <- list(
+  `RFID` = c(1, 2, 3),
+  `IRBB` = c(1, 2, 3)
+)
+
+days
+
## $RFID
+## [1] 1 2 3
+## 
+## $IRBB
+## [1] 1 2 3
+
# Ahora necesitas crear un list de los dataframes que quieres usar para crear los archivos. Como ya sabes cómo filtrar dataframes por día, ese código puede ir adentro de los bucles para minimizar la cantidad de código que escribes. Aquí vas a especificar el dataframe que usarás en las operaciones de filtrar para cada tipo de sensor
+dats <- list(
+  `RFID` = sim_dats_rfid,
+  `IRBB` = sim_dats_irbb
+)
+
+glimpse(dats)
+
## List of 2
+##  $ RFID:'data.frame':    42 obs. of  7 variables:
+##   ..$ chamber_id: chr [1:42] "Nest_01" "Nest_01" "Nest_01" "Nest_01" ...
+##   ..$ timestamps: chr [1:42] "10:00:00" "10:05:00" "11:00:00" "11:05:00" ...
+##   ..$ year      : num [1:42] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:42] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:42] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ PIT_tag   : chr [1:42] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" ...
+##   ..$ sensor_id : chr [1:42] "RFID" "RFID" "RFID" "RFID" ...
+##  $ IRBB:'data.frame':    81 obs. of  6 variables:
+##   ..$ chamber_id: chr [1:81] "Nest_01" "Nest_01" "Nest_01" "Nest_01" ...
+##   ..$ timestamps: chr [1:81] "09:59:59" "10:05:01" "10:59:59" "11:05:01" ...
+##   ..$ year      : num [1:81] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:81] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:81] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ sensor_id : chr [1:81] "Outer Beam Breakers" "Outer Beam Breakers" "Outer Beam Breakers" "Outer Beam Breakers" ...
+

Cuando hayas establecido las estructuras de los datos para informar +la operación del bucle, puedes escribir el bucle anidado mismo. Este +bucle anidado es una estructura compleja, y por ende es útil probar el +bucle con valores determinados de cada variable de iteración +(x y y abajo).

+

Después de escribir este bucle pero antes de ejecutar la estructura +completa del bucle, deberías de probar el código adentro de cada capa +del bucle. Para lograr esto, puedes inicializar los valores de los +variables de iteración y luego correr el código adentro de cada bucle +(pero sin ejecutar el bucle entero). Esta forma de revisar el bucle es +equivalente a congelar el bucle en el tiempo, para que puedas ver el +resultado del código para una sola iteración (abajo vas a ver la primera +iteración para cada capa del bucle cuando ambos x y +y tiene el valor numérico de uno).

+

Para lograr este tipo de chequeo, deberías de ejecutar el código para +“congelar” las variables de iteración en la primera iteración de cada +bucle ( o sea, inicializar x y y con el valor +de uno). Luego deberías de ejecutar el código adentro de cada bucle, +empezando con la creación de days_tmp, luego las +operaciones de indexar y filtrar el dataframe, y luego +filtrar los nombres de los archivos. No deberías de ejecutar las líneas +con lapply() porque quieres evitar ejecutar los bucles +completos hasta que estés segura que el código adentro de cada bucle +funciona de la forma que esperas.

+

Nota importante: Arriba aprendiste que las variables de +iteración no existen afuera de un bucle. Esta forma de probar el código +adentro del bucle, en que no estás ejecutando los bucles mismos, es +equivalente a probar el código afuera del bucle y por ende, los valores +de las variables de iteración que inicialices afuera del bucle se van a +respetar.

+

Mientras revisas el código adentro de cada bucle, deberías de ver que +entre el bucle exterior y el bucle interior, vas a usar el nombre del +sensor para la primera iteración del bucle exterior (“RFID”) para +determinar los días para las iteraciones del bucle interior (en +days_tmp). Adentro del bucle interior, vas a filtrar el +list de dataframes por sensor, y luego por los +días que quieres por sensor. Luego vas a indexar el nombre del archivo +para el tipo de sensor actual y el día actual con una combinación de +indexar la lista de nombres de los archivos con uno o dos pares de +corchetes. Abajo, las líneas que abren y cierran los bucles mismos están +comentados para guiar tu chequeo (o sea para guiar cuales líneas de +código deberías de ejecutar):

+
# Congela las variables de iteración para el chequeo
+x <- 1
+y <- 1
+
+# El bucle exterior: empieza con iterar a través de los sensores
+# invisible(lapply(1:length(sensores), function(x){
+  
+  # Para obtener los días correctos para el tipo de sensor actual, deberías de indexar el list nombrado de días
+  # Este paso de indexar es importante para que el bucle interior ejecute correctamente
+  sensors[x] # Una secuencia de caracteres con el nombre del sensor
+  
+  # Coloca la secuencia de caracteres con el nombre del sensor adentro de dos pares de corchetes para extraer el vector de días para el tipo de sensor actual
+  days_tmp <- days[[sensors[x]]]
+  
+  days_tmp
+  
+  # El bucle interior: itera a través de días para cada tipo de sensor
+  # lapply(1:length(days_tmp), function(y){
+    
+    # Para obtener el dataframe para el tipo de sensor actual, puedes usar x adentro de dos pares de corchetes para extraer el dataframe del list
+    dats[[x]] %>%
+      # Para filtrar el dataframe por el día actual puedes usar y para indexar el vector temporal de días (para extraer un solo elemento de este vector)
+      dplyr::filter(day == days_tmp[y]) %>% 
+      glimpse()
+    
+    # Usa dos pares de corchetes para acceder el vector de los nombres de los archivos para el tipo de sensor actual que está adentro de la lista. Luego usa la variable y para indexar este vector con un par de corchetes para acceder el nombre de archivo correcto para esta iteración
+    files[[x]]
+    
+    files[[x]][y]
+    
+    # También vas a combinar el nombre del archivo con el path correcto:
+    file.path(file_dirs[[x]], files[[x]][y])
+    
+  # })
+  
+# }))
+

Ahora deberías de tener una mejor idea sobre cómo cada bucle opera a +través de diferentes estructuras de datos para realizar la tarea que +quieres (en este caso, escribir una hola de cálculo por tipo de sensor y +día). Luego puedes modificar la estructura entera de los bucles para +reemplazar las líneas que escribiste para el chequeo con las operaciones +finales que quieres realizar:

+
# El bucle exterior: empieza con iterar a través de los sensores
+invisible(lapply(1:length(sensors), function(x){
+  
+  # Para obtener los días correctos para el tipo de sensor actual, deberías de indexar el list nombrado de días
+  # Este paso de indexar es importante para que el bucle interior ejecute correctamente
+  days_tmp <- days[[sensors[x]]]
+  
+  # El bucle interior: itera a través de días para cada tipo de sensor para escribir una hoja de cálculo por tipo de sensor y día
+  lapply(1:length(days_tmp), function(y){
+    
+    # Usa la variable x para acceder el dataframe para el tipo de sensor actual y luego usar la variable y para filtrar este dataframe por el día actual
+    dats[[x]] %>%
+      # Filtra el dataframe por el día actual
+      dplyr::filter(day == days_tmp[y]) %>%
+      # Usa una operación de indexar con doble corchetes para especificar el path correcto por tipo de sensor
+      # Luego usa operaciones de filtrar con doble corchetes y un par de corchetes para acceder el nombre correcto del archivo para el tipo de sensor y el día actual
+      write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE)
+    
+  })
+  
+}))
+

Esta estructura de bucle debería de haber creado un archivo por tipo +de sensor y día en el directorio correcto por tipo de sensor. Puedes +revisar que estos seis archivos existen adentro del directorio para cada +tipo de sensor en tu directorio de trabajo:

+
list.files(file.path(path, "Data/RFID"))
+
+list.files(file.path(path, "Data/IRBB"))
+

En este tutorial aprendiste más sobre cómo filtrar +dataframes y guardar estos objetos como hojas de cálculo, y +cómo usar bucles de una capa y estructuras de bucles anidados. En el +siguiente tutorial vas a usar las hojas de cálculo de los datos +simulados de RFID y sensores infrarrojo que creaste para empezar a +procesar y analizar datos con las funciones de ABISSMAL.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd new file mode 100644 index 0000000..6760e8a --- /dev/null +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.Rmd @@ -0,0 +1,503 @@ +--- +title: "Tutorial 05: Procesar Datos y Crear Gráficas" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". + +

Resumen del tutorial y objetivos de aprendizaje

+ +En este quinto tutorial, vas a empezar a usar las detecciones simuladas de movimientos de animales en el `pipeline` de análisis de datos de ABISSMAL, incluyendo combinar los datos originales a través de días y procesar o limpiar los datos originales. También vas a crear gráficas de los datos procesados. Vas a continuar usando habilidades que aprendiste en los tutoriales anteriores, y vas a aprender nuevas habilidades cómo: + +1. Acceder funciones customizadas +2. Usar funciones customizadas +3. Crear gráficas con `ggplot` + +

Cargar paquetes e inicializar el `path` de tu directorio de trabajo

+ +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Limpia tu ambiente global + +library(tidyverse) # Carga la colección de paquetes del tidyverse +library(data.table) # Carga otros paquetes requeridos por las funciones de ABISSMAL + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo + +``` + +

Cargar las funciones de ABISSMAL

+ +Las funciones customizadas de R en ABISSMAL están guardadas en archivos físicos (extensión .R) adentro de la versión local del repositorio en tu computadora (que descargaste en el primer tutorial). Para poder usar las funciones de ABISSMAL, vas a necesitar cargar los archivos físicos de R para que las funciones estén disponibles en tu ambiente global. En el código de abajo, vas a usar la función `source()` para cargar tres de las cinco funciones primarias de ABISSMAL, y también un archivo que contiene funciones de apoyo: +```{r} + +# Carga la función que combina los datos originales +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") + +# Carga la función que detecta eventos de pose en los datos originales +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") + +# Carga la función que procesa los datos originales +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") + +# Carga un archivo con funciones de apoyo que cada función anterior que requiera +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +

Accede información sobre las funciones de ABISSMAL

+ +Después de ejecutar las líneas de código anteriores, deberías de ver que una colección entera de funciones ahora está disponible en tu ambiente global (revisa la pestaña de `Environment`). Las funciones que empiezan con `check_` son funciones de apoyo. Si haces scroll hacia abajo, puedes ver que tres de las funciones primarias de ABISSMAL (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) también están disponibles en tu ambiente global. En la columna al lado derecho de los nombres de las funciones también podrás ver algo de información sobre los argumentos de cada función. + +Para obtener más información sobre cada una de las tres funciones primarias, puedes hacer clic en el ícono blanco cuadrado a la mera derecha de cada función en la pestaña de `Environment`, o ejecutar el código `View(nombre_de_la_funcion)`. Este comando debería de abrir el archivo de la función actual en una pestaña nueva adentro de tu panel de fuente. En el archivo de cada función, vas ver líneas de documentación que empiezan con los símbolos "`#@", luego el nombre de la función y una descripción, y luego una descripción de cada argumento (parámetro) para la función. Si haces scroll hacia abajo, podrás ver una sección con detalles sobre la función misma, incluyendo la información que devuelve. Esta documentación está escrita solo en inglés por el momento. Después de las líneas de documentación verás el código de la función misma. + +

Combinar los datos originales

+ +Cuando hayas cargado las funciones de ABISSMAL, podrás usar la primera función, `combine_raw_data()`, para combinar los datos colectados a través de días y los tipos de sensores en una sola hoja de cálculo por sensor. Vas a empezar con combinar los datos originales para el sensor de RFID que fueron colectados a través de días diferentes en una hoja de cálculo para este sensor. + +Vas a proveerle información a la función `combine_raw_data()` a través de los siguientes argumentos: + +* `sensors` es un vector que contiene las etiquetas de los tipos de sensores para los cuales quieres combinar los datos originales. Abajo vas a especificar RFID como un solo sensor + +* `path` es tu directorio general de trabajo + +* `data_dir` es la carpeta que contiene archivos dentro de tu directorio de trabajo + +* `out_dir` es la carpeta donde quieres guardar la hoja de cálculo de los datos originales combinados. La función creará esta carpeta si no existe en tu computadora + +* `tz` es la zona de tiempo para convertir marcas de tiempo al formato `POSIXct` en R. La zona de tiempo por defecto es "America/New York", y puedes ver la sección de "Time zones" en la documentación para `DateTimeClasses` en R para más información (`?DateTimeClasses`) + +* `POSIXct_format` es una secuencia de caracteres que contiene la información del formato `POSIXct` para combinar fechas y marcas de tiempo en una sola columna. Por defecto la función devolverá el año como un número con cuatro dígitos y el mes y el día como números con dos dígitos, separados por guiones. La fecha y el tiempo estarán separados por un espacio. La hora, el minuto, y el segundo (en decimales), todos con dos dígitos, estarán separados por dos puntos. +```{r} + +# Combina los datos originales para los sensores de RFID e infrarrojo en procesos separados +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Puedes revisar que `combine_raw_data()` guardó una hoja de cálculo con los datos originales combinados de RFID al directorio nuevo `raw_combined`: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +Puedes proveer el archivo de datos combinados de RFID ("combined_raw_data_RFID.csv") a R para revisar la estructura de esta hoja de cálculo: +```{r} + +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) + +glimpse(rfid_data) + +``` + +R lee esos datos y crea un objeto `dataframe`. Deberías de poder ver que hay columnas nuevas creadas por la función, como la columna `data_type`. Para esta hoja de cálculo, las columnas `sensor_id` y `data_type` contienen la misma información, pero es útil tener columnas separadas para poder estar al tanto de la identidad única del sensor y el tipo de sensor cuando usas múltiples sensores del mismo tipo (por ejemplo, dos pares de sensores infrarrojo tendrán números de identidad únicos en la columna de `sensor_id`). + +La función `combine_raw_data()` también crea una columna nueva de marcas de tiempo en formato `POSIXct` para los siguientes pasos de procesar y analizar datos, pero a la vez mantiene la columna original de marcas de tiempo. Por consiguiente, la función añadió columnas para indicar la etapa de procesar datos y la fecha en que combinaste los datos originales. Finalmente, si revisas las carpetas con los datos originales de RFID, verás que las hojas de cálculo originales por día se preservaron y no fueron ni eliminados ni sobrescritos. + +También puedes ejecutar `combine_raw_data()` con los datos originales de múltiples sensores a la vez proveyendo un vector con las etiquetas de estos sensores al argumento `sensores`. Los datos para cada tipo de sensor se guardarán en hojas de cálculo separadas, y así evitas tener que escribir el mismo código varias veces para ejecutar `combine_raw_data()` para múltiples sensores: +```{r} + +combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Los datos de RFID se sobrescribirán, y deberías de ver una hoja de cálculo adicional con los datos originales de los sensores infrarrojo: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +

Detectar eventos de pose

+ +Puedes usar los datos originales combinados de sensores diferentes en las siguientes funciones de ABISSMAL para empezar a hacer inferencias de comportamiento de los datos de detección de movimiento. Por ejemplo, puedes detectar eventos de pose en los datos originales de RFID con la función `detect_perching_events()`. Puedes leer más sobre cada argumento en el archivo de R que contiene esta función. +```{r} + +detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +`detect_perching_events()` puede operar en sólo un archivo y tipo de sensor a la vez. La función automáticamente crea una carpeta que se llama "processed" (para datos procesados) y guardará un archivo de `.csv` dentro de esa carpeta si efectivamente pudo detectar eventos de pose usando el umbral temporal actual (`threshold`, en segundos), y la duración de secuencias de detección actual (`run_length`, en número de detecciones). + +Cuando creamos datos simulados en los últimos tutoriales, simulaste eventos de pose en los datos de RFID. Pudiste recuperar estos eventos de pose usando `detect_perching_events()`? +```{r} + +perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) + +glimpse(perching) + +``` + +`detect_perching_events()` identificó un total de seis eventos de pose, que es el mismo número que simulaste en el tutorial anterior (dos eventos de pose por día a través de tres días). Puedes revisar los valores dentro del `dataframe` para ver más información sobre estos eventos de pose: +```{r} + +# Las marcas de tiempo cuando cada pose empezó +perching$perching_start + +# Las marcas de tiempo cuando cada pose terminó +perching$perching_end + +# La etiqueta única de PIT que contiene información sobre la identidad del individuo que estuvo posando en la antena de RFID +perching$PIT_tag + +``` + +También puedes visualizar el `dataframe` entero en un panel separado: +```{r eval = FALSE} + +View(perching) + +``` + +La información de arriba te dice que hubo dos eventos de pose a las 8:00 cada día, y dos eventos de pose a las 11:30 cada día (como esperábamos). La etiqueta de PIT para cada individuo se detectó una vez por día, por ende, cada individuo realizó un evento de pose cada día. + +Detectar eventos de pose no es un requisito en el `pipeline` de análisis de ABISSMAl, pero puede ser un paso útil para obtener la mayor cantidad de información que puedes de los datos originales antes de filtrar las detecciones en el siguiente paso de procesar o limpiar los datos originales. + +

Procesar los datos originales

+ +Cuando hayas detectado los eventos de pose en los datos originales, puedes seguir con procesar o limpiar los datos originales con la función `preprocess_detections()`. Los datos originales a veces contienen múltiples detecciones separadas por poco tiempo (como las detecciones de RFID cuando un individuo está posando en la antena), y estas múltiples detecciones pueden causar ruido cuando tratas de hacer inferencias de comportamiento con datos colectados por múltiples sensores. Cuando le provees "thin" al argumento `mode`, `preprocess_detections()` elimina detecciones separadas por un periodo corto de tiempo (usando el valor del umbral temporal en segundos para el argumento `thin_threshold`), y devuelve datos filtrados de detecciones que todavía representan eventos discretos de movimiento. + +`preprocess_detections()` opera en un solo tipo de sensor a la vez: +```{r} + +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Ahora deberías de ver un archivo de `.csv` adicional que se llama "pre_processed_data_RFID.csv" en la carpeta "processed": +```{r} + +list.files(file.path(path, "Data/processed")) + +``` + +Puedes darle este archivo a R para que lo lea y vea su estructura. Deberías de poder ver menos filas en este `dataframe` comparado con la hoja de cálculo de los datos originales: +```{r} + +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) + +glimpse(rfid_pp) + +``` + +Luego puedes procesar los datos originales de los sensores infrarrojo y revisar el archivo de `.csv`: +```{r} + +preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +list.files(file.path(path, "Data/processed")) + +irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) + +glimpse(irbb_pp) + +``` + +

Visualizar datos de RFID en una gráfica de barras

+ +Ahora que combinaste y procesaste los datos originales por sensor, es hora de visualizar estos conjuntos de datos diferentes. Hacer gráficas mientras escribes código es importante para generar figures de alta calidad para publicaciones y presentaciones y también para revisar tu proceso de analizar datos. + +En el código de abajo, vas a aprender cómo usar funciones del paquete `ggplot2` para hacer una gráfica de barras con los datos originales y procesados de RFID. + +Puedes empezar dándole a R los datos originales y procesados de RFID, los eventos de posar de RFID, y convertir las marcas de tiempo al formato `POSIX`. +```{r} + +rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer gráficas + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + # Ordena las marcas de tiempo + # La expresión "-desc()" dentro de la función arrange() indica que las marcas de tiempo se ordenarán de menos a más recientes + dplyr::arrange(-desc(timestamp_ms)) + +# Deberías de ver que la columna timestamp_ms con las marcas de tiempo está en el formato "dttm", significando que la conversión a formato POSIX se realizó correctamente +glimpse(rfid_raw) + +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se le dan a leer a R para hacer gráficas + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + dplyr::arrange(-desc(timestamp_ms)) + +glimpse(rfid_pp) + +rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% + # Tienes que convertir las marcas de tiempo de inicio y final de cada evento de pose al formato POSIX cada vez que los datos se dan a R para hacer gráficas + dplyr::mutate( + perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), + perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + dplyr::arrange(-desc(perching_start)) + +glimpse(rfid_perch) + +``` + +Luego puedes combinar los datos originales y procesados en un solo `dataframe` para facilitar visualizar todos estos datos en la misma gráfica. Vas a añadir una columna nueva (`dataset`) con etiquetas para identificar los dos conjuntos de datos diferentes. +```{r} + +rfid_combined <- rfid_raw %>% + dplyr::select(sensor_id, day, timestamp_ms) %>% + dplyr::mutate( + dataset = "raw" + ) %>% + bind_rows( + rfid_pp %>% + dplyr::select(sensor_id, day, timestamp_ms) %>% + dplyr::mutate( + dataset = "pre-processed" + ) + ) %>% + dplyr::arrange(-desc(timestamp_ms)) + +glimpse(rfid_combined) + +``` + +Vas a construir la gráfica con estos datos usando funciones de `ggplot2`, un paquete que es parte del `tidyverse` pero que también se puede instalar y usar fuera del `tidyverse`. Puedes revisar este [enlace](https://ggplot2.tidyverse.org/) que tiene más recursos (en inglés) para aprender cómo usar la notación de `ggplot` para hacer diferentes tipos de gráficas. Estos recursos incluyen secciones de tres libros diferentes con ejercicios para practicar hacer gráficas sencillas o complejas, y también un curso en línea y un seminario en línea. Puedes encontrar otros recursos en español también, como esta [guia para `ggplot2`](https://raw.githubusercontent.com/rstudio/cheatsheets/main/translations/spanish/data-visualization_es.pdf). + +El paquete de `ggplot2` tiene una notación única para construir gráficas, en la cual empiezas haciendo la gráfica llamando la función `ggplot()` y luego añades características añadiendo capas de otras funciones de `ggplot2` con el símbolo de `+`. + +Si llamas `ggplot()`, verás que la función inmediatamente dibuja una gráfica vacía en tu panel de `Plots` (gráficas) en RStudio. +```{r} + +ggplot() + +``` + +La gráfica seguirá vacía incluso cuando le provees información sobre tus datos para poder configurar la estética en los siguientes pasos. +```{r} + +ggplot(data = rfid_combined) + +``` + +Necesitarás añadir otras funciones estéticas a esta capa fundamental de la gráfica para poder ver tus datos. Las funciones que usarás para añadir detalles estéticos a la gráfica vacía dependerán del tipo de gráfica que quieres crear. En este ejemplo, vas a generar una gráfica barras, en la cual cada marca de tiempo está representada por una línea vertical delgada. Las gráfica barras pueden ser gráficas útiles cuando trabajas con marcas de tiempo, porque la información más importante se contiene en una dimensión (el tiempo en el eje "x"). Si fueras a resumir el número de marcas de tiempo grabado cada día, sería mejor hacer una gráfica de líneas. + +Puedes añadir la función `geom_segment()` como la siguiente capa encima de la capa fundamental de la gráfica. `geom_segment()` facilita añadir línea a una gráfica, y las líneas pueden comunicar información en una o dos dimensiones (por su ancho en el eje "x" y su altura en el eje "y"). +```{r} + +ggplot(data = rfid_combined) + + + # Añade una línea vertical para cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + +``` + +En el código anterior, `geom_segment()` añade una línea vertical a la gráfica para cada detección en el `dataframe` completo. Usando el argumento `color` adentro de `geom_segment()`, y proveyendo el nombre de la columna que contiene las etiquetas de los conjuntos de datos, le comunicaste a la función que las líneas deberían de tener colores que corresponden al conjunto particular de datos. El argumento `color` tiene que estar adentro de la función `aes()` (que controla la estética de esta capa de información) para que esta asignación de colores se realice por el conjunto de datos. + +Los colores de las líneas se asignarán automáticamente por `ggplot` usando los colores por defecto del paquete, pero puedes cambiar estos colores usando la función `scale_color_manual()`: +```{r} + +ggplot(data = rfid_combined) + + + # Añade una línea vertical para cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + +``` + +Las líneas ahora tienen los colores nuevos que especificaste pero en la leyenda los datos procesados salen primero. Si quieres cambiar el orden de las etiquetas del conjunto de datos en la leyenda, y los colores asignados a los conjuntos de datos, puedes modificar la columna `dataset` en el `dataframe` que usaste para crear la gráfica. + +Las funciones de `ggplot` usan un tipo de datos que se llaman `factors` para determinar automáticamente la estética de la gráfica, como el método de asignar colores que usaste arriba con `geom_segment()`. Las columnas (o vectores) en formato `factor` se ven como columnas de tipo `character` (en que cada fila contiene una secuencia de caracteres), pero R guarda los valores únicos de cada columna como números enteros y luego guarda los valores únicos de las secuencias de caracteres en una propiedad que se llama `levels` ("niveles" o "categorías"). Puedes cambiar el orden en que los valores únicos de una columna en formato `factor` se añaden a la gráfica cuando cambias el orden de los `levels` de la columna: +```{r} + +# Cambia la columna dataset al tipo de datos "factor" +# Cuando especificas que el valor de "raw" ("original") vaya primero en el argumento de levels, estás reorganizando los niveles de la columna para que este valor salga primero en la leyenda +rfid_combined <- rfid_combined %>% + dplyr::mutate( + dataset = factor(dataset, levels = c("raw", "pre-processed")) + ) + +# La columna de dataset ahora es tipo "fct" o "factor" +glimpse(rfid_combined) + +# Los niveles de la columna ahora están ordenados para que el valor "raw" vaya primero, en vez de estar en orden alfabético +levels(rfid_combined$dataset) + +``` + +Después de convertir la columna de `dataset` al tipo `factor` y reorganizar los `levels` de los valores únicos en esta columna, las categorías de esta columna deberían de aparecer en el orden correcto en la leyenda de la gráfica y no en orden alfabético. También deberías de ver que los colores asignados a cada conjunto de datos acaba de cambiar. +```{r} + +ggplot(data = rfid_combined) + + + # Añade una línea vertical para cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + +``` + +En la gráfica que acabas de hacer, puede resultar difícil distingüir entre las líneas para cada conjunto de datos. Puedes usar la función `facet_wrap()` para dividir los conjuntos de datos en paneles diferentes: +```{r} + +ggplot(data = rfid_combined) + + + # Añade una línea vertical para cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # El símbolo de ~ significa "por", así que estás creando un panel por cada valor único (o categoría) en la columna dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + +``` + +Acabas de crear paneles diferentes dentro de esta gráfica y cada panel contiene información de un solo conjunto de datos. Con este cambio estructural también alineaste los paneles en el mismo eje "x" para que sea más fácil de comparar patrones temporales. + +Hasta este punto es difícil ver cómo los dos conjuntos de datos (originales y procesados) son diferentes. Puedes filtrar el `dataframe` con funciones del `tidyverse` para visualizar solo las primeras dos detecciones para cada conjunto de datos: +```{r} + +ggplot(data = rfid_combined %>% + # Crea grupos para las categorías o levels en la columna dataset + group_by(dataset) %>% + # Selecciona las primeras dos filas para cada grupo + slice(1:2) %>% + ungroup() + ) + + + # Añade una línea vertical para cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # El símbolo de ~ significa "por", así que ahora estás creando un panel por cada valor único (o categoría) en la columna dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + +``` + +Ahora deberías de ver que la segunda marca de tiempo en los datos originales se eliminó del conjunto de datos procesados (fue filtrado usando el umbral temporal en `preprocess_detections`). + +Luego puedes añadir los datos de eventos de pose en el `dataframe` `rfid_perch`, que no combinaste con los otros conjuntos de datos. A diferencia de `rfid_raw` y `rfid_pp`, este conjunto de datos tiene dos columnas de marcas de tiempo que contienen información sobre el inicio y el fin de cada evento de pose. Puedes añadir este conjunto de datos a la gráfica volviendo a llamar la función `geom_segment()`. Usarás esta capa de `geom_segment()` para añadir líneas que contienen información temporal sobre cuándo empezaron los eventos de pose y cuándo terminaron. +```{r} + +ggplot(data = rfid_combined) + + + # Añade una línea vertical por cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # El símbolo de ~ significa "por", así que estás creando un panel por cada valor único (o categoría) en la columna dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + + + # Añade los eventos de pose + geom_segment( + data = rfid_perch, + aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), + color = "blue", + linewidth = 0.3 + ) + +``` + +En el código anterior con respecto a `geom_segment()`, especificaste que querías añadir otro conjunto de datos a la gráfica cuando usaste el argumento `data`. Los argumentos `x` y `y` determinan donde va a empezar cada línea en ambos ejes de la gráfica, respectivamente. También vas a tener que especificar donde quieres que cada línea termina en cada eje. En el eje x, indicaste que quieres que la línea empiece y termine cuando los eventos de pose empezaron y terminaron, proveyendo a la columna `perching_start` al argumento `x` y `perching_end` al argumento `xend`. En el eje "y", los números que usaste para los argumentos `y` y `yend` determinan donde las líneas para los eventos de pose se van a dibujar, que en este caso es justamente arriba de las líneas para los otros conjuntos de datos. Las líneas para los eventos de pose se dibujaron como otra capa de información encima de cada panel de la gráfica por defecto. + +Puedes hacer unos cambios a la gráfica para que sea más fácil de interpretar. Puedes cambiar la posición de la leyenda usando el argumento `legend.position` adentro de la función general de `theme()`. Abajo puedes guardar la gráfica adentro de un objeto, para que no tengas que escribir otra vez el código de la gráfica cada vez que quieres añadirle más información. +```{r} + +gg <- ggplot(data = rfid_combined) + + + # Añade una línea vertical para cada marca de tiempo + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # El símbolo de ~ significa "por", así que estás ahora creando un panel por cada valor único (o categoría) en la columna dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + + + # Añade los eventos de pose + geom_segment( + data = rfid_perch, + aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), + color = "blue", + linewidth = 0.3 + ) + + + theme( + legend.position = "top" + ) + +gg + +``` + +Ahora puedes hacer ajustes menores para seguir mejorando la gráfica, incluyendo cambiar los títulos de los ejes para que sean más informativos, cambiar el color de fondo a blanco, y eliminar el texto en el eje "y" así como las líneas en el eje "y", porque este eje no contiene información para interpretación de los datos (o sea, la altura de cada línea no contiene información para interpretación). +```{r} + +gg <- gg + + + # Cambia los títulos de ambos ejes + xlab("Date and time") + + + # El eje "y" no contiene información y por ende puedes eliminar este título + ylab("") + + + # Usa esta función para cambiar el color de fondo a blanco y negro + theme_bw() + + + # Usa funciones de estética para eliminar el texto en el eje "y" y también las líneas en este eje + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + +gg + +``` + +Puedes guardar las gráficas que creas en R como archivos físicos. Abajo usarás la función `ggsave()` para escribir la gráfica que hiciste arriba y guardarlo en tu computadora. +```{r} + +gg + +# Guarda la gráfica como un archivo en tu computadora +ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +Puedes continuar con modificaciones a este archivo para crear una figura de alta calidad para una publicación. Por ejemplo, puedes cambiar el tamaño final del archivo (`width` o "el ancho", `height` o "la altura"), así como también la resolución en píxeles (`dpi`). Incluso puedes cambiar el tamaño de texto en cada eje o del título de cada eje, o la posición de la leyenda mientras determinas el tamaño final de la imagen. + +En el siguiente tutorial vas a continuar analizando datos con ABISSMAL y vas a crear una gráfica de barras más compleja y refinada. \ No newline at end of file diff --git a/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html new file mode 100644 index 0000000..8331e4a --- /dev/null +++ b/R/vignettes/Tutorial_05_ProcesarDatos_CrearGraficas.html @@ -0,0 +1,2330 @@ + + + + + + + + + + + + + + + +Tutorial 05: Procesar Datos y Crear Gráficas + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, +siguiendo las convenciones +de traducción de los Carpentries, incluyendo usar el género femenino +por defecto. Si encuentras errores de ortografía que impiden tu +habilidad de completar los tutoriales, por favor reporta los errores de +ortografía a GitHub usando los pasos en el primer tutorial para reportar +un “Issue”.

+

+Resumen del tutorial y objetivos de aprendizaje +

+

En este quinto tutorial, vas a empezar a usar las detecciones +simuladas de movimientos de animales en el pipeline de +análisis de datos de ABISSMAL, incluyendo combinar los datos originales +a través de días y procesar o limpiar los datos originales. También vas +a crear gráficas de los datos procesados. Vas a continuar a usar +habilidades que aprendiste en los tutoriales anteriores, y vas a +aprender nuevas habilidades cómo:

+
    +
  1. Acceder funciones customizadas
  2. +
  3. Usar funciones customizadas
  4. +
  5. Crear gráficas con ggplot
  6. +
+

+Cargar paquetes e inicializar el path de tu directorio de +trabajo +

+
rm(list = ls()) # Limpia tu ambiente global
+
+library(tidyverse) # Carga la colección de paquetes del tidyverse
+library(data.table) # Carga otros paquetes requeridos por las funciones de ABISSMAL
+
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo
+

+Cargar las funciones de ABISSMAL +

+

Las funciones customizadas de R en ABISSMAL están guardadas en +archivos físicos (extensión .R) adentro de la versión local del +repositorio en tu computadora (que descargaste en el primer tutorial). +Para poder usar las funciones de ABISSMAL, vas a necesitar cargar los +archivos físicos de R para que las funciones estén disponibles en tu +ambiente global. En el código abajo, vas a usar la función +source() para cargar tres de las cinco funciones primarias +de ABISSMAL, y también un archivo que contiene funciones de apoyo:

+
# Carga la función que combina los datos originales
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R")
+
+# Carga la función que detecta eventos de posa en los datos originales
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R")
+
+# Carga la función que procesa los datos originales
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R")
+
+# Carga un archivo con funciones de apoyo que cada función arriba requiere
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")
+

+Accede información sobre las funciones de ABISSMAL +

+

Después de ejecutar las líneas de código arriba, deberías de ver que +una colección entera de funciones ahora está disponible en tu ambiente +global (revisa la pestaña de Environment). Las funciones +que empiezan con check_ son funciones de apoyo. Si haces +scroll hacia abajo, puedes ver que tres de las funciones primarias de +ABISSMAL (combine_raw_data, +detect_perching_events, preprocess_detections) +también están disponibles en tu ambiente global. En la columna al lado +derecho de los nombres de las funciones también podrás ver algo de +información sobre los argumentos de cada función.

+

Para obtener más información sobre cada una de las tres funciones +primarias, puedes hacer clic en el ícono blanco cuadrado a la mera +derecha de cada función en la pestaña de Environment, o +ejecutar el código View(nombre_de_la_funcion). Este comando +debería de abrir el archivo de la función actual en una pestaña nueva +adentro de tu panel de fuente. En el archivo de cada función, vas ver +líneas de documentación que empiezan con los símbolos “`#@”, luego el +nombre de la función y una descripción, y luego una descripción de cada +argumento (parámetro) para la función. Si haces scroll hacia abajo, +podrás ver una sección con detalles sobre la función misma, incluyendo +la información que devuelve. Esta documentación está escrita en inglés +por el momento. Después de las líneas de documentación verás el código +de la función misma.

+

+Combinar los datos originales +

+

Cuando hayas cargado las funciones de ABISSMAL, podrás usar la +primera función, combine_raw_data(), para combinar los +datos colectados a través de días y los tipos de sensores en una sola +hoja de cálculo por sensor. Vas a empezar con combinar los datos +originales para el sensor de RFID que fueron colectados a través de días +diferentes en una hoja de cálculo para este sensor.

+

Vas a proveerle información a la función +combine_raw_data() a través de los siguientes +argumentos:

+
    +
  • sensors es un vector que contiene las etiquetas de +los tipos de sensores para los cuales quieres combinar los datos +originales. Abajo vas a especificar RFID como un solo sensor

  • +
  • path es tu directorio general de trabajo

  • +
  • data_dir es la carpeta que contiene datos adentro de +tu directorio de trabajo

  • +
  • out_dir es la carpeta donde quieres guardar la hoja +de cálculo de los datos originales combinados. La función creará esta +carpeta si no existe en tu computadora

  • +
  • tz es la zona de tiempo para convertir marcas de +tiempo al formato POSIXct en R. La zona de tiempo por +defecto es “America/New York”, y puedes ver la sección de “Time zones” +en la documentación para DateTimeClasses en R para más +información (?DateTimeClasses)

  • +
  • POSIXct_format es una secuencia de caracteres que +contiene la información del formato POSIXct para combinar +fechas y marcas de tiempo en una sola columna. Por defecto la función +devolverá el año como un número con cuatro dígitos y el mes y el día +como números con dos dígitos, separados por guiones. La fecha y el +tiempo estarán separados por un espacio. La hora, el minuto, y el +segundo (en decimales), todos con dos dígitos, estarán separados por dos +puntos.

  • +
+
# Combina los datos originales para los sensores de RFID e infrarrojo en procesos separados
+combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

Puedes revisar que combine_raw_data() guardó una hoja de +cálculo con los datos originales combinados de RFID al directorio nuevo +raw_combined:

+
list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")
+
## [1] "combined_raw_data_IRBB.csv" "combined_raw_data_RFID.csv"
+

Puedes leer el archivo de datos combinados de RFID +(“combined_raw_data_RFID.csv”) a R para revisar la estructura de esta +hoja de cálculo:

+
rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv"))
+
+glimpse(rfid_data)
+
## Rows: 42
+## Columns: 11
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, …
+## $ original_timestamp <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08…
+## $ timestamp_ms       <chr> "2023-08-01 10:00:00", "2023-08-01 10:05:00", "2023…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ data_stage         <chr> "raw_combined", "raw_combined", "raw_combined", "ra…
+## $ date_combined      <chr> "2024-04-07 2024-04-07 19:19:42.298508", "2024-04-0…
+

Leer estos datos a R creó un objeto dataframe. Deberías +de poder ver que hay unas columnas nuevas creadas por la función, como +la columna data_type. Para esta hoja de cálculo, las +columnas sensor_id y data_type contienen la +misma información, pero es útil tener columnas separadas para poder +estar al tanto de la identidad única del sensor y el tipo de sensor +cuando usas múltiples sensores del mismo tipo (por ejemplo, dos pares de +sensores infrarrojo tendrán números de identidad únicos en la columna de +sensor_id).

+

La función combine_raw_data() también creó una columna +nueva de marcas de tiempo en formato POSIXct para los pasos +que siguen de procesar y analizar datos, pero mantuvo la columna +original de marcas de tiempo. La función añadió columnas para indicar la +etapa de procesar datos y la fecha en que combinaste los datos +originales. Finalmente, si revisas las carpetas con los datos originales +de RFID, verás que las hojas de cálculo originales por día se +preservaron y no fueron ni eliminados ni sobrescritos.

+

También puedes ejecutar combine_raw_data() con los datos +originales de múltiples sensores a la vez con proveer un vector con las +etiquetas de estos sensores al argumento sensores. Los +datos para cada tipo de sensor todavía se guardarán en hojas de cálculo +separadas, y así evitas tener que escribir el mismo código varias veces +para ejecutar combine_raw_data() para múltiples +sensores:

+
combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

Los datos de RFID se sobrescribirán, y deberías de ver una hoja de +cálculo adicional con los datos originales de los sensores +infrarrojo:

+
list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")
+
## [1] "combined_raw_data_IRBB.csv" "combined_raw_data_RFID.csv"
+

+Detectar eventos de posar +

+

Puedes usar los datos originales combinados de sensores diferentes en +las siguientes funciones de ABISSMAL para empezar a hacer inferencias de +comportamiento de los datos de detección de movimiento. Por ejemplo, +puedes detectar eventos de posar en los datos originales de RFID con la +función detect_perching_events(). Puedes leer más sobre +cada argumento en el archivo de R que contiene esta función.

+
detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

detect_perching_events() puede operar en sólo un archivo +y tipo de sensor a la vez. La función automáticamente crea una carpeta +que se llama “processed” (para datos procesados) y guardará un archivo +de .csv adentro de esa carpeta si pudo detectar eventos de +posar usando el umbral temporal actual (threshold, en +segundos), y la duración de secuencias de detección actual +(run_length, en número de detecciones).

+

Cuando creamos datos simulados en los últimos tutoriales, simulaste +eventos de posar en los datos de RFID. Pudiste recuperar estos eventos +de posar usando detect_perching_events()?

+
perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv"))
+
+glimpse(perching)
+
## Rows: 6
+## Columns: 11
+## $ chamber_id              <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "N…
+## $ sensor_id               <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID"
+## $ PIT_tag                 <chr> "1357aabbcc", "2468zzyyxx", "1357aabbcc", "246…
+## $ perching_start          <chr> "2023-08-01 08:00:00", "2023-08-01 11:30:00", …
+## $ perching_end            <chr> "2023-08-01 08:00:03", "2023-08-01 11:30:05", …
+## $ perching_duration_s     <int> 3, 5, 3, 5, 3, 5
+## $ unique_perching_event   <int> 1, 2, 3, 4, 5, 6
+## $ min_perching_run_length <int> 2, 2, 2, 2, 2, 2
+## $ threshold_s             <int> 2, 2, 2, 2, 2, 2
+## $ data_stage              <chr> "pre-processing", "pre-processing", "pre-proce…
+## $ date_preprocessed       <chr> "2024-04-07 2024-04-07 19:19:42.840588", "2024…
+

detect_perching_events() identificó un total de seis +eventos de posar, que es el mismo número que simulaste en el tutorial +anterior (dos eventos de posar por día a través de tres días). Puedes +revisar los valores adentro del dataframe para ver más +información sobre estos eventos de posar:

+
# Las marcas de tiempo cuando cada evento de posar empezó
+perching$perching_start
+
## [1] "2023-08-01 08:00:00" "2023-08-01 11:30:00" "2023-08-02 08:00:00"
+## [4] "2023-08-02 11:30:00" "2023-08-03 08:00:00" "2023-08-03 11:30:00"
+
# Las marcas de tiempo cuando cada evento de posar terminó
+perching$perching_end
+
## [1] "2023-08-01 08:00:03" "2023-08-01 11:30:05" "2023-08-02 08:00:03"
+## [4] "2023-08-02 11:30:05" "2023-08-03 08:00:03" "2023-08-03 11:30:05"
+
# La etiqueta única de PIT que contiene información sobre la identidad del individuo que estuvo posando en la antena de RFID
+perching$PIT_tag
+
## [1] "1357aabbcc" "2468zzyyxx" "1357aabbcc" "2468zzyyxx" "1357aabbcc"
+## [6] "2468zzyyxx"
+

También puedes visualizar el dataframe entero en un +panel separado:

+
View(perching)
+

La información arriba te dice que hubo dos eventos de posar a las +8:00 cada día, y dos eventos de posar a las 11:30 cada día (como +esperamos). La etiqueta de PIT para cada individuo se detectó una vez +por día, por ende, cada individuo realizó un evento de posar cada +día.

+

Detectar eventos de posar no es un requisito en el +pipeline de análisis de ABISSMAl, pero puede ser un paso +útil para obtener la mayor cantidad de información que puedes de los +datos originales antes de filtrar las detecciones en el siguiente paso +de procesar o limpiar los datos originales.

+

+Procesar los datos originales +

+

Cuando hayas detectado los eventos de posar en los datos originales, +puedes seguir con procesar o limpiar los datos originales con la función +preprocess_detections(). Los datos originales a veces +contienen múltiples detecciones separadas por poco tiempo (como las +detecciones de RFID cuando un individuo está posando en la antena), y +estas múltiples detecciones pueden causar ruido cuando tratas de hacer +inferencias de comportamiento con datos colectados por múltiples +sensores. Cuando le provees “thin” al argumento mode, +preprocess_detections() elimina detecciones separadas por +un periodo corto de tiempo (usando el valor del umbral temporal en +segundos para el argumento thin_threshold), y devuelve +datos filtrados de detecciones que todavía representan eventos discretos +de movimiento.

+

preprocess_detections() opera en un solo tipo de sensor +a la vez:

+
preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

Ahora deberías de ver un archivo de .csv adicional que +se llama “pre_processed_data_RFID.csv” en la carpeta “processed”:

+
list.files(file.path(path, "Data/processed"))
+
## [1] "detection_clusters.csv"       "perching_events_RFID.csv"    
+## [3] "pre_processed_data_IRBB.csv"  "pre_processed_data_RFID.csv" 
+## [5] "scored_detectionClusters.csv"
+

Puedes leer este archivo a R para ver su estructura. Deberías de ver +menos filas en este dataframe comparado con la hoja de +cálculo de los datos originales:

+
rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv"))
+
+glimpse(rfid_pp)
+
## Rows: 27
+## Columns: 11
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ timestamp_ms       <chr> "2023-08-01 08:00:00", "2023-08-01 08:00:02", "2023…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:19:43.177927", "2024-04-0…
+

Luego puedes procesar los datos originales de los sensores infrarrojo +y revisar el archivo de .csv:

+
preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+
+list.files(file.path(path, "Data/processed"))
+
## [1] "detection_clusters.csv"       "perching_events_RFID.csv"    
+## [3] "pre_processed_data_IRBB.csv"  "pre_processed_data_RFID.csv" 
+## [5] "scored_detectionClusters.csv"
+
irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv"))
+
+glimpse(irbb_pp)
+
## Rows: 66
+## Columns: 10
+## $ data_type          <chr> "IRBB", "IRBB", "IRBB", "IRBB", "IRBB", "IRBB", "IR…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms       <chr> "2023-08-01 06:05:04", "2023-08-01 06:05:05", "2023…
+## $ sensor_id          <chr> "Inner Beam Breakers", "Outer Beam Breakers", "Oute…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:19:43.291101", "2024-04-0…
+

+Visualizar datos de RFID en una gráfica de código de barras +

+

Ahora que combinaste y procesaste los datos originales por sensor, es +hora de visualizar estos conjuntos de datos diferentes. Hacer gráficas +mientras escribes código es importante para generar figures de alta +calidad para publicaciones y presentaciones y también para revisar tu +proceso de analizar datos.

+

En el código abajo, vas a aprender cómo usar funciones del paquete +ggplot2 para hacer una gráfica del estilo de código de +barras con los datos originales y procesados de RFID.

+

Puedes empezar con leer los datos originales y procesados de RFID, +también los eventos de posar de RFID, y convertir las marcas de tiempo +al formato POSIX.

+
rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>%
+  # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer gráficas
+  dplyr::mutate(
+    timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>%
+  # Ordena las marcas de tiempo
+  # La expresión "-desc()" adentro de la función arrange() indica que las marcas de tiempo se ordenarán de menos a más recientes
+  dplyr::arrange(-desc(timestamp_ms))
+
+# Deberías de ver que la columna timestamp_ms con las marcas de tiempo está en el formato "dttm", significando que la conversión a formato POSIX se realizó bien
+glimpse(rfid_raw)
+
## Rows: 42
+## Columns: 11
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, …
+## $ original_timestamp <chr> "08:00:00", "08:00:01", "08:00:02", "08:00:03", "10…
+## $ timestamp_ms       <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:01, 2023-08-…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ data_stage         <chr> "raw_combined", "raw_combined", "raw_combined", "ra…
+## $ date_combined      <chr> "2024-04-07 2024-04-07 19:19:42.46078", "2024-04-07…
+
rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>%
+  # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer gráficas
+  dplyr::mutate(
+    timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>% 
+  dplyr::arrange(-desc(timestamp_ms))
+
+glimpse(rfid_pp)
+
## Rows: 27
+## Columns: 11
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ timestamp_ms       <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:02, 2023-08-…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:19:43.177927", "2024-04-0…
+
rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>%
+  # Tienes que convertir las marcas de tiempo de inicio y final de cada evento de posar al formato POSIX cada vez que los datos se leen a R para hacer gráficas
+  dplyr::mutate(
+    perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")),
+    perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>% 
+  dplyr::arrange(-desc(perching_start))
+
+glimpse(rfid_perch)
+
## Rows: 6
+## Columns: 11
+## $ chamber_id              <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "N…
+## $ sensor_id               <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID"
+## $ PIT_tag                 <chr> "1357aabbcc", "2468zzyyxx", "1357aabbcc", "246…
+## $ perching_start          <dttm> 2023-08-01 08:00:00, 2023-08-01 11:30:00, 2023…
+## $ perching_end            <dttm> 2023-08-01 08:00:03, 2023-08-01 11:30:05, 202…
+## $ perching_duration_s     <int> 3, 5, 3, 5, 3, 5
+## $ unique_perching_event   <int> 1, 2, 3, 4, 5, 6
+## $ min_perching_run_length <int> 2, 2, 2, 2, 2, 2
+## $ threshold_s             <int> 2, 2, 2, 2, 2, 2
+## $ data_stage              <chr> "pre-processing", "pre-processing", "pre-proc…
+## $ date_preprocessed       <chr> "2024-04-07 2024-04-07 19:19:42.840588", "2024…
+

Luego puedes combinar los datos originales y procesados en un solo +dataframe para facilitar visualizar todos estos datos en la +misma gráfica. Vas a añadir una columna nueva (dataset) con +etiquetas para identificar los dos conjuntos de datos diferentes.

+
rfid_combined <- rfid_raw %>%
+  dplyr::select(sensor_id, day, timestamp_ms) %>% 
+  dplyr::mutate(
+    dataset = "raw"
+  ) %>% 
+  bind_rows(
+    rfid_pp %>%
+      dplyr::select(sensor_id, day, timestamp_ms) %>% 
+      dplyr::mutate(
+        dataset = "pre-processed"
+      ) 
+  ) %>%
+  dplyr::arrange(-desc(timestamp_ms))
+
+glimpse(rfid_combined)
+
## Rows: 69
+## Columns: 4
+## $ sensor_id    <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "…
+## $ day          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:00, 2023-08-01 08:…
+## $ dataset      <chr> "raw", "pre-processed", "raw", "raw", "pre-processed", "r…
+

Vas a construir la gráfica con estos datos usando funciones de +ggplot2, un paquete que es parte del tidyverse +pero que también se puede instalar y usar afuera del +tidyverse. Puedes revisar este enlace que tiene más recursos +(en inglés) para aprender cómo usar la notación de ggplot +para hacer diferentes tipos de gráficas. Estos recursos incluyen +secciones de tres libros diferentes con ejercicios para practicar hacer +gráficas sencillas o complejas, y también un curso en línea y un +seminario en línea. Puedes encontrar otros recursos en español en línea, +como esta guia +para ggplot2.

+

El paquete de ggplot2 tiene una notación única para +construir gráficas, en que empiezas haciendo la gráfica con llamar la +función ggplot() y luego añades características con añadir +capas de otras funciones de ggplot2 con el símbolo de ++.

+

Si llamas ggplot(), verás que la función inmediatamente +dibuja una gráfica vacía en tu panel de Plots (gráficas) en +RStudio.

+
ggplot()
+

+

La gráfica seguirá vacía incluso cuando le provees información sobre +tus datos para poder configurar la estética en los siguientes pasos.

+
ggplot(data = rfid_combined)
+

+

Necesitarás añadir otras funciones estéticas a esta capa fundamental +de la gráfica para poder ver tus datos. Las funciones que usarás para +añadir detalles estéticos a la gráfica vacía dependerán del tipo de +gráfica que quieres crear. En este ejemplo, vas a generar una gráfica de +código de barras, en que cada marca de tiempo está representada por una +línea vertical delgada. Las gráfica de código de barra pueden ser +gráficas útiles cuando trabajas con marcas de tiempo, porque la +información más importante se contiene en una dimensión (el tiempo en el +eje x). Si fueras a resumir el número de marcas de tiempo grabado cada +día, sería mejor hacer una gráfica de líneas.

+

Puedes añadir la función geom_segment() como la +siguiente capa encima de la capa fundamental de la gráfica. +geom_segment() facilita añadir línea a una gráfica, y las +líneas pueden comunicar información en una o dos dimensiones (por su +ancho en el eje x y su altura en el eje y).

+
ggplot(data = rfid_combined) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  )
+

+

En el código arriba, geom_segment() añade una línea +vertical a la gráfica para cada detección en el dataframe +completo. Con usar el argumento color adentro de +geom_segment(), y proveer el nombre de la columna que +contiene las etiquetas de los conjuntos de datos, le comunicaste a la +función que las líneas deberían de tener colores que corresponden al +conjunto particular de datos. El argumento color tiene que +estar adentro de la función aes() (que controla la estética +de esta capa de información) para que esta asignación de colores se +realice por el conjunto de datos.

+

Los colores de las líneas se asignarán automáticamente por +ggplot usando los colores por defecto del paquete, pero +puedes cambiar estos colores usando la función +scale_color_manual():

+
ggplot(data = rfid_combined) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen"))
+

+

Las líneas ahora tienen los colores nuevos que especificaste pero en +la leyenda los datos procesados salen primero. Si quieres cambiar el +orden de las etiquetas del conjunto de datos en la leyenda, y los +colores asignados a los conjuntos de datos, puedes modificar la columna +dataset en el dataframe que usaste para crear +la gráfica.

+

Las funciones de ggplot usan un tipo de datos que se +llaman factors para automáticamente determinar la estética +de la gráfica, como el método de asignar colores que usaste arriba con +geom_segment(). Las columnas (o vectores) en formato +factor se ven como columnas de tipo character +(en que cada fila contiene una secuencia de caracteres), pero R guarda +los valores únicos de cada columna como números enteros y luego guarda +los valores únicos de las secuencias de caracteres en una propiedad que +se llama levels (“niveles” o “categorías”). Puedes cambiar +el orden en que los valores únicos de una columna en formato +factor se añaden a la gráfica cuando cambias el orden de +los levels de la columna:

+
# Cambia la columna dataset al tipo de datos "factor"
+# Cuando especificas que el valor de "raw" ("original") venga primero en el argumento de levels, estás reorganizando los levels (niveles) de la columna para que este valor salga primero en la leyenda
+rfid_combined <- rfid_combined %>% 
+  dplyr::mutate(
+    dataset = factor(dataset, levels = c("raw", "pre-processed"))
+  )
+
+# La columna de dataset ahora es tipo "fct" o "factor"
+glimpse(rfid_combined)
+
## Rows: 69
+## Columns: 4
+## $ sensor_id    <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "…
+## $ day          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:00, 2023-08-01 08:…
+## $ dataset      <fct> raw, pre-processed, raw, raw, pre-processed, raw, raw, pr…
+
# Los niveles de la columna ahora están ordenados para que el valor "raw" venga primero, en vez de estar en orden alfabético
+levels(rfid_combined$dataset)
+
## [1] "raw"           "pre-processed"
+

Después de convertir la columna de dataset al tipo +factor y reorganizar los levels de los valores +únicos en esta columna, las categorías de esta columna deberían de +aparecer en el orden correcto en la leyenda de la gráfica y no en orden +alfabético. También deberías de ver que los colores asignados a cada +conjunto de datos acaba de cambiar.

+
ggplot(data = rfid_combined) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen"))
+

+

En la gráfica que acabas de hacer, es muy difícil de discriminar +entre las líneas para cada conjunto de datos. Puedes usar la función +facet_wrap() para dividir los conjuntos de datos en paneles +diferentes:

+
ggplot(data = rfid_combined) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # El símbolo de ~ significa "por", así que estás creando un panel por cada valor único (o categoría) en la columna dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left")
+

+

Acabas de crear paneles diferentes adentro de esta gráfica y cada +panel contiene datos un solo conjunto de datos. Con este cambio +estructural también alineaste los paneles en el mismo eje x para que sea +más fácil de comparar patrones temporales.

+

Desde este punto de vista es difícil ver cómo los dos conjuntos de +datos (originales y procesados) son diferentes. Puedes filtrar el +dataframe con funciones del tidyverse para +visualizar solo las primeras dos detecciones para cada conjunto de +datos:

+
ggplot(data = rfid_combined %>%
+         # Crea grupos por las categorías o levels en la columna dataset
+         group_by(dataset) %>% 
+         # Selecciona las primeras dos filas para cada grupo 
+         slice(1:2) %>% 
+         ungroup()
+       ) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # El símbolo de ~ significa "por", así que estás creando un panel por cada valor único (o categoría) en la columna dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left")
+

+

Ahora deberías de ver que la segunda marca de tiempo en los datos +originales se eliminó del conjunto de datos procesados (fue filtrado +usando el umbral temporal en preprocess_detections).

+

Luego puedes añadir los datos de eventos de posar en el +dataframe rfid_perch, que no combinaste con +los otros conjuntos de datos. En cambio a rfid_raw y +rfid_pp, este conjunto de datos tiene dos columnas de +marcas de tiempo que contienen información sobre el inicio y el fin de +cada evento de posar. Puedes añadir este conjunto de datos a la gráfica +con otra llamada de la función geom_segment(). Usarás esta +capa de geom_segment() para añadir líneas que contienen +información temporal sobre cuándo los eventos de posar empezaron y +terminaron.

+
ggplot(data = rfid_combined) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # El símbolo de ~ significa "por", así que estás creando un panel por cada valor único (o categoría) en la columna dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left") +
+  
+  # Añade los eventos de posar
+  geom_segment(
+    data = rfid_perch,
+    aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5),
+    color = "blue",
+    linewidth = 0.3
+  )
+

+

En el código arriba para geom_segment(), especificaste +que querías añadir otro conjunto de datos a la gráfica cuando usaste el +argumento data. Los argumentos x y +y determinan donde va a empezar cada línea en ambos ejes de +la gráfica, respectivamente. También vas a tener que especificar donde +quieres que cada línea termina en cada eje. En el eje x, indicaste que +quieres que la línea empiece y termine cuando los eventos de posar +empezaron y terminaron con proveer la columna +perching_start al argumento x y +perching_end al argumento xend. En el eje y, +los números que usaste para los argumentos y y +yend determinan donde las líneas para los eventos de posar +se dibujarán, que en este caso es justamente arriba de las líneas para +los otros conjuntos de datos. Las líneas para los eventos de posar se +dibujaron como otra capa de información encima de cada panel de la +gráfica por defecto.

+

Puedes hacer unos cambios a la gráfica para que sea más fácil de +interpretar. Puedes cambiar la posición de la leyenda usando el +argumento legend.position adentro de la función general de +theme(). Abajo puedes guardar la gráfica adentro de un +objeto, para que no tengas que escribir todo el código de la gráfica de +nuevo cuando quieres añadirle más información.

+
gg <- ggplot(data = rfid_combined) +
+  
+  # Añade una línea vertical para cada marca de tiempo
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # El símbolo de ~ significa "por", así que estás creando un panel por cada valor único (o categoría) en la columna dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left") +
+  
+  # Añade los eventos de posar
+  geom_segment(
+    data = rfid_perch,
+    aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5),
+    color = "blue",
+    linewidth = 0.3
+  ) +
+  
+  theme(
+    legend.position = "top"
+  )
+
+gg
+

+

Ahora puedes hacer unos ajustes menores para seguir mejorando la +gráfica, incluyendo cambiar los títulos de los ejes para que sean más +informativos, cambiar el color de fondo a blanco, y eliminar el texto en +el eje y tanto como las rayas en el eje y, porque este eje no contiene +información para interpretación de los datos (o sea, la altura de cada +línea no contiene información para interpretación).

+
gg <- gg +
+  
+  # Cambia los títulos de ambos ejes
+  xlab("Date and time") +
+  
+  # El eje y no contiene información y por ende puedes eliminar este título
+  ylab("") +
+  
+  # Usa esta función para cambiar el color de fondo a blanco y negro
+  theme_bw() +
+  
+  # Usa funciones de estética para eliminar el texto en el eje y y también los rayos en este eje
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  )
+
+gg
+

+

Puedes guardar las gráficas que creas en R como archivos físicos. +Abajo usarás la función ggsave() para escribir la gráfica +que hiciste arriba como un archivo en tu computadora.

+
gg
+

+
# Guarda la gráfica como un archivo en tu computadora
+ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300)
+

Puedes continuar con modificaciones a este archivo para crear una +figura de alta calidad para una publicación. Por ejemplo, puedes cambiar +el tamaño final del archivo (width o “el ancho”, +height o “la altura”), tanto como la resolución en píxeles +(dpi). También puedes cambiar el tamaño de texto en cada +eje o del título de cada eje, o la posición de la leyenda mientras +determinas el tamaño final de la imagen.

+

En el siguiente tutorial vas a continuar analizando datos con +ABISSMAL y vas a crear una gráfica de código de barras más compleja y +refinada.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd new file mode 100644 index 0000000..e6c68e6 --- /dev/null +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.Rmd @@ -0,0 +1,560 @@ +--- +title: "Tutorial 06: Finalizar Los Analisis" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Información sobre esta traducción

+ +Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, siguiendo las [convenciones de traducción de los Carpentries](https://github.com/Carpentries-ES/board/blob/master/Convenciones_Traduccion.md), incluyendo usar el género femenino por defecto. Si encuentras errores de ortografía que impiden tu habilidad de completar los tutoriales, por favor reporta los errores de ortografía a GitHub usando los pasos en el primer tutorial para reportar un "Issue". + +

Resumen del tutorial y objetivos de aprendizaje

+ +En este sexto y último tutorial, vas a terminar de usar las detecciones simuladas de movimientos de animales en el `pipeline` de procesar y analizar datos de ABISSMAL. Vas a detectar `clusters` ("cúmulos") de detecciones que representan eventos de movimientos distintos y luego vas a anotar inferencias de comportamiento de estos eventos de movimiento. También vas a crear gráficas para visualizar las inferencias de comportamiento. Vas a continuar a usar habilidades que aprendiste en los tutoriales anteriores, y también vas a aprender cómo crear visualizaciones más complejas con `ggplot()`. + +

Cargar paquetes e inicializar el `path` de tu directorio de trabajo

+ +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Limpia tu ambiente global + +library(tidyverse) # Carga la colección de paquetes del tidyverse +library(data.table) # Carga otros paquetes requeridos por las funciones de ABISSMAL + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo + +``` + +

Cargar funciones de ABISSMAL

+ +```{r} + +# Carga la función que detecta clusters en los datos procesados +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") + +# Carga la función que anota inferencias de comportamiento sobre los clusters +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R") + +# Carga un archivo con funciones de apoyo que cada función de arriba requiera +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +

Termina el `pipeline` de ABISSMAL

+ +Aquí usarás la función de ABISSMAL que se llama `detect_clusters()` para identificar `clusters` de detecciones a través de los tipos de sensores (detecciones de diferentes sensores que fueron grabados juntos en el tiempo). +```{r} + +# El argumento run_length, o lo largo de cada serie de detecciones que ocurrieron juntos en el tiempo (cluster) tiene que ser 1 para poder detectar clusters con un largo de 2 detecciones +detect_clusters(file_nms = c("pre_processed_data_RFID.csv", "pre_processed_data_IRBB.csv"), threshold = 2, run_length = 1, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", preproc_metadata_col_nms = c("thin_threshold_s", "data_stage", "date_pre_processed"), general_metadata_col_nms = c("chamber_id", "year", "month", "day"), path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "detection_clusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Luego vas a anotar inferencias de comportamiento de estos `clusters` de detecciones con la función `score_clusters()`. +```{r} + +score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_label = NULL, outer_irbb_label = "Outer Beam Breakers", inner_irbb_label = "Inner Beam Breakers", video_metadata_col_nms = NULL, integrate_perching = TRUE, perching_dataset = "RFID", perching_prefix = "perching_events_", sensor_id_col_nm = "sensor_id", PIT_tag_col_nm = "PIT_tag", pixel_col_nm = NULL, video_width = NULL, video_height = NULL, integrate_preproc_video = FALSE, video_file_nm = NULL, timestamps_col_nm = NULL, path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "scored_detectionClusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +

Revisa los resultados finales

+ +Puedes revisar los resultados finales ahora que terminaste de ejecutar el `pipeline` de ABISSMAl para detectar `clusters` y generar inferencias de comportamiento. +```{r} + +scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% + # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer gráficas + dplyr::mutate( + start = as.POSIXct(format(as.POSIXct(start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), + end = as.POSIXct(format(as.POSIXct(end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + # Ordena las marcas de tiempo + dplyr::arrange(-desc(start)) + +glimpse(scored_clusters) + +``` + +Cuántos eventos de entrada y salida se anotaron por día? + +

Datos ausentes en R

+ +Para poder contar la cantidad de cada uno de estos eventos que ABISSMAL anotó por día, necesitas saber cómo manejar datos ausentes en R. Los datos ausentes se suelen representar usando el valor `NA` ("not available" o "no disponible"), que es un tipo de dato específico en R. Puedes determinar si un vector (o una columna en un `dataframe`) contiene datos ausentes si usas la función `is.na()`, que devolverá `TRUE` cuando encuentra un valor de `NA` (un valor ausente) en el vector actual. +```{r} + +?is.na() + +x <- c(1, NA, 2, 3, NA) + +is.na(x) + +``` + +En el código de arriba, creaste un vector que se llama `x` que tiene dos valores de `NA`. La función `is.na()` revisa si cada elemento de `x` es equivalente a `NA`, y devuelve `TRUE` cuando se cumple esa condición (o sea, cuando encuentra un dato ausente). + +Como `is.na()` es una frase condicional, también puedes usar otros símbolos especiales que son relevantes para las frases condicionales, como el símbolo de `!`, que sirve para invertir una frase condicional. Por ejemplo, en el código de abajo, cuando añades `!` antes de `is.na()`, estás preguntando si cada elemento de `x` *no* es equivalente a `NA`: +```{r} + +!is.na(x) + +``` + +Como puedes ver, añadir el `!` en frente del `is.na()` resulta en valores binarios invertidos comparado con usar solo `is.na()`, y ahora cada valor que antes era `TRUE` se convirtió en `FALSE`. A la par, la habilidad de invertir la frase condicional de `is.na()`, y el resultado binario que devuelve `is.na()` son propiedades muy útiles para encontrar y filtrar las filas de un `dataframe`. + +Por ejemplo, la función `dplyr::filter()` eliminará una fila cada vez que encuentra un valor de `FALSE` en la columna actual y en cambio, no eliminará una fila cada vez que encuentra un valor de `TRUE`. O sea, si quieres eliminar files que contienen valores de `NA` para una columna actual, añadirías `!is.na(name_of_column)` adentro de `dplyr::filter()`, que debería de devolver `FALSE` cada vez que encuentra una fila con `NA`, y eliminará esa fila como parte de la operación de filtrar. + +

Contar eventos por día

+ +Ahora puedes escribir código para contar el número de eventos de entrada y de salida que ABISSMAL anotó por día. Vas a necesitar 1) crear una columna nueva con la información sobre el día para cada marca de tiempo, 2) eliminar filas con datos ausentes para la información anotada de la dirección del movimiento (la columna `direction_scored`, porque esta información no se puede anotar para algunos movimientos), 3) agrupar el `dataframe` por día y por la dirección anotada, y luego 4) contar el número de filas por grupo. +```{r} + +scored_clusters %>% + # Extrae el día de cada marca de tiempo y crea una columna nueva con esta información + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Aquí estas usando la función is.na() que devolverá TRUE cuando encuentra un valor ausente (NA) en la columna actual. Colocando el símbolo de ! antes de is.na(), estás invirtiendo la frase condicional y también el resultado, así que todos los valores TRUE se convertirán a FALSE. Por ende, dplyr::filter eliminará todas las filas que devuelven el valor de FALSE en esta expresión (o sea, todas las filas con valores ausentes en la columna direction_scored con información sobre la dirección anotada de movimiento) + dplyr::filter(!is.na(direction_scored)) %>% + # Agrupa el dataframe de ambas columnas para las cuales quieres contar filas (eventos). Aquí quieres saber el número de entradas y salidas (categorías en la columna direction_scored) por día (categorías en la columna "day") + group_by(day, direction_scored) %>% + # Luego puedes resumir los datos. El número de filas aquí es el número de entradas o salidas anotadas por día + dplyr::summarise( + n = n() + ) + +``` + +Cuatro entradas y salidas se anotaron por día. Cómo se compara este resultado con el número de entradas y salidas que esperabas por día? Si regresas a los tutoriales anteriores (el tercer y el cuarto tutorial) donde creaste los datos originales simulados, deberías de poder ver que empezaste por simular dos entradas y dos salidas por día en los datos para el sistema de RFID y los sensores de infrarrojo. Luego añadiste dos entradas y dos salidas más por día cuando simulaste fallas de detección del sistema de RFID (es decir, estos fueron movimientos capturados solamente por los sensores infrarrojo). `score_clusters` detectó el número correcto de entradas y salidas por día. + +Ahora puedes revisar los eventos de pose, filtrando todas las filas que no se anotaron como eventos de pose (o sea, filas con valores de `NA` en la columna "perching_PIT_tag" que contiene información sobre las etiquetas de PIT). Luego puedes seleccionar solo las columnas que sí contienen información que es útil revisar, como las identidades de las etiquetas PIT, y también las marcas de tiempo para el inicio y fin de cada pose. +```{r} + +scored_clusters %>% + # Usa una frase condicional con is.na() para retener solo las filas que tienen códigos de etiquetas PIT que fueron asociados con eventos de posar + dplyr::filter(!is.na(perching_PIT_tag)) %>% + # Luego selecciona solo las columnas que quieres revisar visualmente + dplyr::select(start, end, perching_PIT_tag) + +``` + +Como aprendiste en el tercer y el cuarto tutorial, el primer evento de pose por día se realizó por el primer individuo (con la etiqueta de PIT "1357aabbcc"), y el segundo evento de pose por día fue realizado por el segundo individuo (con la etiqueta de PIT "2468zzyyxx"). + +Cuántos eventos de movimiento que no fueron eventos de pose fueron asignados a cada individuo? +```{r} + +scored_clusters %>% + # Extrae el día de cada marca de tiempo y crea una columna nueva con esta información + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Usa una frase condicional con is.na() para retener solo las filas que tienen códigos de las etiquetas de PIT que no fueron asociados con eventos de pose + # Aquí estás combinando dos frases condicionales para poder buscar filas en la columna individual_initiated (el individuo que inició el movimiento) que tienen códigos de etiquetas PIT, pero que también no tienen una etiqueta de sensor en la columna perching_sensor (o sea, eventos de movimiento que no fueron eventos de pose, sino entradas o salidas) + dplyr::filter(!is.na(individual_initiated) & is.na(perching_sensor)) %>% + group_by(day, individual_initiated) %>% + dplyr::summarise( + n = n() + ) + +``` + +Como puedes ver, cuatro eventos de movimiento que no fueron eventos de pose se asignaron al primer individuo (con la etiqueta PIT "1357aabbcc") en cada día, que es exactamente lo que simulaste en el tercer y el cuarto tutorial. Más movimientos que no fueron eventos de pose se detectaron a través de estos días también, pero como esos eventos no fueron capturados por la antena de RFID (o sea fueron fallas simuladas de la antena de RFID), estos movimientos fueron capturados por sólo los sensores infrarrojo que no pueden grabar información sobre la identidad de los individuos. + +

Construye una gráfica de barras compleja

+ +Ahora puedes visualizar los resultados finales. En el código de abajo vas a aprender cómo crear una gráfica de barras que es más compleja de lo que viste en el tutorial anterior, pero que será más fácil de interpretar. Para esta gráfica sería útil poder visualizar tres tipos de inferencias de comportamiento o tipos de información a través del tiempo: la dirección de movimiento (cuando esté disponible), la identidad del individuo (cuando esté disponible), y los eventos de posar. + +Puedes empezar con construir la gráfica añadiendo líneas verticales para los eventos que no fueron eventos de pose. El color de cada línea indicará la identidad del individuo, incluyendo cuando la información no estaba disponible. El tipo de línea va a contener información sobre la dirección de movimiento, y también cuando la información no se pudo anotar. + +Para poder asignar colores y tipos de líneas de la forma que quieres en la gráfica, vas a necesitar modificar el `dataframe` de los resultados finales para convertir los `NAs` en las dos columnas asociadas con estos detalles de estética para convertir los datos ausentes en información útil. Por ejemplo, cuando no hay información sobre la identidad del individuo, sería muy útil convertir los valores asociados de `NA` a un valor como "unassigned" ("no asignado"). En el código abajo vas a usar la función `is.na()` dentro de frases condicionales de `ifelse()` para convertir los valores de `NA` dentro de las columnas "individual_initiated" y "direction_scored" para poder tener información útil para la gráfica que vas a hacer más adelante. + +Primero puedes practicar usar `is.na()` dentro de `ifelse()` para crear un vector nuevo. En el código siguiente, vas a proveer la frase condicional que quieres probar (aquí vas a probar si la columna que contiene las etiquetas PIT del individuo que inició el movimiento tiene valores de `NA`), el valor que quieres añadir al vector si la condición se cumple (el valor "unassigned" cuando no hay información sobre la etiqueta PIT), y luego el valor que quieres añadir si la condición no se cumple (en este caso es devolver la etiqueta PIT en la columna individual_initiated si el valor actual no es `NA`). +```{r} + +# is.na() devuelve TRUE cuando encuentra valores de NA adentro de un vector (o columna) y FALSE cuando el valor actual no es NA +is.na(scored_clusters$individual_initiated) + +# Puedes ver que todos los valores de NA adentro de esta columna se convirtieron a "unassigned" pero todos los otros valores no cambiaron +ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) + +``` + +Ahora puedes usar `is.na()` dentro de frases de `ifelse()` para modificar columnas en el `dataframe`. +```{r} + +scored_clusters_gg <- scored_clusters %>% + dplyr::mutate( + # Si esta columna tiene un valor de NA, cambia el valor actual a "unassigned" + # Pero si el valor actual no es NA, no cambies el valor actual de esta columna + individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated), + # Repite este proceso para la columna direction_scored pero con un valor diferente (aquí "not_scored" significa que la dirección no se pudo anotar) + # En la frase condicional de abajo añadiste is.na(perching_sensor) (después del símbolo de &) para sólo convertir los valores de NA en la columna de direction_scored si tampoco fueron anotados como eventos de pose en la columna perching_sensor + direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored) + ) %>% + # Luego vas a convertir cada una de estas columnas al tipo factor y ordenar los levels (categorías) para crear la gráfica de la forma que queremos (por ejemplo, los valores de "unassigned" y "not scored" deberían de ser los últimos valores en la leyenda) + dplyr::mutate( + individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")), + direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored")) + ) + +# Revisa los cambios que hiciste arriba usando la función distinct() para ver que todos los valores únicos de cada columna fueron modificados correctamente +# Los valores de NA en la columna de direction_scored son esperados porque se refieren a los eventos de pose +scored_clusters_gg %>% + distinct(individual_initiated, direction_scored) + +``` + +Este resultado se ve bien. Deberías de ver valores de `NA` en el `dataframe` pero están en la columna "direction_scored" y asociados con los eventos de pose, que vas a añadir a la gráfica en otra capa diferente de código más adelante. + +Puedes usar este `dataframe` modificado para crear la gráfica. En el código de abajo, vas a añadir líneas con colores asignados por la identidad de individuos, y los tipos de línea van a representar la dirección de movimiento. Primero vas a especificar los detalles estéticos de la gráfica: +```{r} + +# Los colores están en el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranja va a representar la etiqueta PIT "1357aabbcc" +levels(scored_clusters_gg$individual_initiated) +cols <- c("orange", "darkgreen", "black") + +# Los tipos de línea están en el mismo orden que los niveles de la columna direction_scored, así que el valor "dotted" ("línea punteada") va a representar "not scored" (cuando la dirección no se pudo anotar) +levels(scored_clusters_gg$direction_scored) +ltys <- c("solid", "longdash", "dotted") + +``` + +Luego puedes añadir líneas a la gráfica por cada identidad de individuo. Aquí estás dividiendo las llamadas de `geom_segment()` por los `levels` (categorías o valores únicos) en la columna individual_initiated. Solo añadiste líneas para el primer individuo y todos los movimientos que no fueron eventos de pose y que no fueron asignados a ese individuo porque después de revisar los resultados finales, sabrás que ningún movimiento (que no fue evento de pose) fueron asignados al segundo individuo. +```{r} + +ggplot() + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo + geom_segment( + data = scored_clusters_gg %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que no fue asignado a uno de los dos individuos + geom_segment( + data = scored_clusters_gg %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Añade los tipos de línea customizados a la gráfica + scale_linetype_manual(values = ltys) + + + # Elimina el título del eje "y" + ylab("") + + + # Usa esta función para convertir el fondo de la gráfica a blanco y negro + theme_bw() + + + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda dentro de la gráfica + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + +``` + +Para hacer esta gráfica, organizaste las líneas verticales para separar los movimientos asignados al primer individuo y los movimientos que no fueron asignados. Esta separación vertical entre estos dos conjuntos de datos facilita las comparaciones visuales de variación en los patrones temporales de los movimientos. Creaste esta separación vertical al cambiar los valores que usaste para `y` y `yend` en la segunda capa de `geom_segment()` para que esas líneas empezarían más arriba que las líneas de la primera capa de `geom_segment()`. + +Puedes hacer más modificaciones que ayudarán a la interpretación de esta gráfica. En primer lugar, las etiquetas de los paneles que están al lado izquierdo se pueden cambiar para demostrar el día general de colección de datos (como "Day 1" para el primer día) en vez de la fecha. El texto en en eje "x" también se puede cambiar para demostrar sólo el tiempo (o sea, eliminar la información sobre el mes y el día), y también puedes añadir más etiquetas (por ejemplo, una etiqueta cada media hora). + +Puedes empezar modificando el `dataframe` para añadir la información sobre el día de colección de datos. Para ello, vas a crear una columna nueva usando frases condicionales a través de la función `ifelse()` porque sólo hay tres días de colección de datos con etiquetas que tienes que cambiar. +```{r} + +# Crea una columna nueva en los datos originales para la fecha de colección de datos +scored_clusters_gg2 <- scored_clusters_gg %>% + # Primero necesitas crear una columna con información sobre el día + dplyr::mutate( + day = lubridate::day(start) + ) %>% + dplyr::mutate( + # Luego deberías de reemplazar la etiqueta para cada día y guardar estos resultados en una columna nueva + day_label = ifelse(day == 1, "Day 1", day), # Aquí el último argumento es la columna day porque la columna day_label no se ha creado todavía + day_label = ifelse(day == 2, "Day 2", day_label), + day_label = ifelse(day == 3, "Day 3", day_label) + ) + +# Se ve bien? +glimpse(scored_clusters_gg2) + +scored_clusters_gg2 %>% + distinct(day_label) + +``` + +Ahora puedes actualizar el código para incluir las etiquetas nuevas de las fechas: +```{r} + +# Añade el dataframe como el conjunto de datos por defecto para la capa fundamental de la gráfica para que la función facet_wrap() tenga datos +ggplot(data = scored_clusters_gg2) + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo + geom_segment( + data = scored_clusters_gg2 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que no fue asignado a ninguno de los dos individuos + geom_segment( + data = scored_clusters_gg2 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Añade los tipos de línea customizados a la gráfica + scale_linetype_manual(values = ltys) + + + # Elimina el título del eje "y" + ylab("") + + + # Usa esta función para convertir el fondo de la gráfica a blanco y negro + theme_bw() + + + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda adentro de la gráfica + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + + + # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas del día + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +``` + +Ahora que moviste la información del día de colección de datos a las etiquetas de los paneles de la gráfica, pero necesitas componer el texto en el eje "x". La gráfica será más fácil de interpretar si puedes alinear las marcas de tiempo por hora y por minuto para una comparación directa a través de los diferentes días. + +Para alinear las marcas de tiempo a través de los días, necesitas actualizar el formato de las columnas que contienen las marcas de tiempo. El código para convertir las marcas de tiempo a un formato diferente es anidado y repetitivo pero la conversión se realizará correctamente. Cuando le comunicas a R que deberías de convertir las marcas de tiempo a un formato con horas, minutos, y segundos solamente, R va a añadir un año, un mes, y un día por defecto antes de la marca de tiempo (lo más común es que usará la fecha actual). Este comportamiento es esperado y no es un error, más bien facilita que las marcas de tiempo se alinean de la forma correcta a través de los días (paneles) en la gráfica (porque R considera todas las marcas de tiempo como si ocurrieron en un solo día). +```{r} + +scored_clusters_gg3 <- scored_clusters_gg2 %>% + dplyr::mutate( + start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")), + end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +# Deberías de ver que un año, mes, y día nuevos fueron adjuntados a las marcas de tiempo modificadas, y este resultado es esperado (arriba) +glimpse(scored_clusters_gg3) + +``` + +Ahora puedes actualizar el código para crear la gráfica cambiando la estética del eje "x" usando la función `scale_x_datetime()` para especificar que quieres etiquetas cada media hora en este eje. También vas a añadir un título para el eje "x" y eliminar la cuadrícula en el eje "y" adentro de cada panel: +```{r} + +gg <- ggplot(data = scored_clusters_gg3) + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que no fue asignado a ninguno de los individuos + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Añade los tipos de línea customizados a la gráfica + scale_linetype_manual(values = ltys) + + + # Elimina el título del eje "y" + ylab("") + + + # Usa esta función para convertir el fondo de la gráfica a blanco y negro + theme_bw() + + + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda dentro de la gráfica + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + + + # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas para "día" + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + + + # Cambia la estética de las etiquetas del eje "x" + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # Añade un título para el eje "x" + xlab("Time of day (HH:MM)") + + + # Puedes quitar la cuadrícula en el eje "y" (mayor y menor) adentro de cada panel + theme( + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank() + ) + +gg + +``` + +Todavía falta un tipo de información importante que necesitas añadir a esta gráfica: los eventos de pose. Vas a añadir esta información con otra capa de `geom_segment()` pero ahora vas a crear líneas cortas y anchas para que aparezcan más como puntos en la gráfica: +```{r} + +gg <- gg + + + # Añade los eventos de pose como líneas con orillas redondeadas, y puedes añadir colores que indican la identidad del individuo a través de la columna individual_initiated + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(!is.na(perching_sensor)), + aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), + linewidth = 2, lineend = "round" + ) + + + # Añade los colores customizados para estos eventos de pose. Los colores también aplican a las líneas de movimientos por individuo que no fueron eventos de pose y que añadiste en capas anteriores de geom_segment() + scale_color_manual(values = cols) + +gg + +``` + +Ahora deberías de ver que una leyenda de color aparece arriba de la gráfica con la adición de esta capa adicional de `geom_segment()`. Ambas de las leyendas se pueden mejorar. Puedes modificar la gráfica actualizando el título de cada leyenda, aumentando el tamaño de texto de cada leyenda, y reduciendo el espacio blanco entre las leyendas y la gráfica. Para modificar los títulos de cada leyenda, vas a usar las funciones `guides()` y `guide_legend()`. Para aumentar el tamaño de texto en la leyenda, vas a usar el argumento `legend.text` adentro de la función `theme()`, y para reducir el espacio blanco entre la gráfica y las leyendas, vas a usar el argumento `legend.margin` adentro de la función `theme()`. +```{r} + +# Ve más información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda +?margin + +gg <- gg + + + # Aumenta el tamaño de texto de cada leyenda y reduce el espacio blanco entre la gráfica y las leyendas + theme( + legend.text = element_text(size = 10), + legend.margin = margin(-1, -1, -1, -1, unit = "pt") + ) + + + # Modifica los títulos de cada leyenda + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") + ) + +gg + +``` + +Finalmente puedes guardar esta gráfica como un archivo: +```{r} + +gg + +# Guarda el archivo con la gráfica en tu computadora +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +Puedes continuar con modificar estéticas menores a este archivo para crear una figura de mayor calidad para una publicación. Por ejemplo, puedes cambiar el tamaño final de la imagen (`width`, `height`), tanto como la resolución (`dpi`). Puedes también cambiar el tamaño de texto en cada eje y los títulos de cada eje, o la posición de la leyenda mientras determinas el tamaño final de la imagen en el archivo. + +Abajo está todo el código que escribiste para la gráfica final, en una forma más condensada y reorganizada: +```{r eval = FALSE} + +ggplot(data = scored_clusters_gg3) + + + # Añade una línea vertical para cada evento que no fue un evento de pose y que fue asignado al primer individuo + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a ninguno de los dos individuos + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Añade los eventos de pose como líneas con orillas redondeadas, y añade colores que indican la identidad del individuo a través de la columna individual_initiated + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(!is.na(perching_sensor)), + aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), + linewidth = 2, lineend = "round" + ) + + + # Añade los tipos de línea customizados a la gráfica + scale_linetype_manual(values = ltys) + + + # Añade los colores customizados para estos eventos de pose. Los colores también aplican a las líneas de movimientos por individuo que no fueron eventos de pose y que añadiste en capas anteriores de geom_segment() + scale_color_manual(values = cols) + + + # Modifica los títulos de cada leyenda + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") + ) + + + # Añade un título para el eje "x" + xlab("Time of day (HH:MM)") + + + # Elimina el título del eje "y" + ylab("") + + + # Cambia la estética de las etiquetas del eje "x" + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas para "día" + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + + + # Usa esta función para convertir el fondo de la gráfica a blanco y negro + theme_bw() + + + # Usa estas funciones de estética para eliminar el texto y los rayos del eje "y" + # Añade un argumento para cambiar la posición de la leyenda dentro de la gráfica + # Puedes quitar la cuadrícula en el eje "y" (mayor y menor) dentro de cada panel + # Aumenta el tamaño de texto de cada leyenda y reduce el espacio blanco entre la gráfica y las leyendas + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top", + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank(), + legend.text = element_text(size = 10), + legend.margin = margin(-1, -1, -1, -1, unit = "pt") + ) + +``` + +Acabas de completar el tutorial final del `pipeline` de procesar y analizar datos de ABISSMAL. También practicaste tus habilidades de programar y tus habilidades de la ciencia de datos en un contexto biológico. Muy bien hecho! Nos ayudaría mucho si puedes completar la forma de Google para una evaluación de estos tutoriales una vez que los hayas terminado. Tus respuestas nos ayudarán a mejorar estos tutoriales en el futuro. Un enlace a la forma de Google estará disponible en el archivo README en el path "R/vignettes/README.md" de este repositorio \ No newline at end of file diff --git a/R/vignettes/Tutorial_06_FinalizarAnalisis.html b/R/vignettes/Tutorial_06_FinalizarAnalisis.html new file mode 100644 index 0000000..c060865 --- /dev/null +++ b/R/vignettes/Tutorial_06_FinalizarAnalisis.html @@ -0,0 +1,2379 @@ + + + + + + + + + + + + + + + +Tutorial 06: Finalizar Los Analisis + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Información sobre esta traducción +

+

Este tutorial fue traducido al español por Grace Smith-Vidaurre y editado por Tania Molina, +siguiendo las convenciones +de traducción de los Carpentries, incluyendo usar el género femenino +por defecto. Si encuentras errores de ortografía que impiden tu +habilidad de completar los tutoriales, por favor reporta los errores de +ortografía a GitHub usando los pasos en el primer tutorial para reportar +un “Issue”.

+

+Resumen del tutorial y objetivos de aprendizaje +

+

En este sexto y último tutorial, vas a terminar de usar las +detecciones simuladas de movimientos de animales el +pipeline de procesar y analizar datos de ABISSMAL. Vas a +detectar clusters (“cúmulos”) de detecciones que +representan eventos de movimientos distintos y luego vas a anotar +inferencias de comportamiento de estos eventos de movimiento. También +vas a crear gráficas para visualizar las inferencias de comportamiento. +Vas a continuar a usar habilidades que aprendiste en los tutoriales +anteriores, y también vas a aprender cómo crear visualizaciones más +complejas con ggplot().

+

+Cargar paquetes e inicializar el path de tu directorio de +trabajo +

+
rm(list = ls()) # Limpia tu ambiente global
+
+library(tidyverse) # Carga la colección de paquetes del tidyverse
+library(data.table) # Carga otros paquetes requeridos por las funciones de ABISSMAL
+
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Inicializa un objeto con el path de tu directorio de trabajo
+

+Cargar funciones de ABISSMAL +

+
# Carga la función que detecta clusters en los datos procesados
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R")
+
+# Carga la función que anota inferencias de comportamiento sobre los clusters
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R")
+
+# Carga un archivo con funciones de apoyo que cada función arriba requiere
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")
+

+Termina el pipeline de ABISSMAL +

+

Aquí usarís la función de ABISSMAL que se llama +detect_clusters() para identificar clusters de +detecciones a través de los tipos de sensores (detecciones de diferentes +sensores que fueron grabados juntos en el tiempo).

+
# El argumento run_length, o lo largo de cada serie de detecciones que ocurrieron juntos en el tiempo (cluster) tiene que ser 1 para poder detectar clusters con un largo de 2 detecciones
+detect_clusters(file_nms = c("pre_processed_data_RFID.csv", "pre_processed_data_IRBB.csv"), threshold = 2, run_length = 1, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", preproc_metadata_col_nms = c("thin_threshold_s", "data_stage", "date_pre_processed"), general_metadata_col_nms = c("chamber_id", "year", "month", "day"), path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "detection_clusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

Luego vas a anotar inferencias de comportamiento de estos +clusters de detecciones con la función +score_clusters().

+
score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_label = NULL, outer_irbb_label = "Outer Beam Breakers", inner_irbb_label = "Inner Beam Breakers", video_metadata_col_nms = NULL, integrate_perching = TRUE, perching_dataset = "RFID", perching_prefix = "perching_events_", sensor_id_col_nm = "sensor_id", PIT_tag_col_nm = "PIT_tag", pixel_col_nm = NULL, video_width = NULL, video_height = NULL, integrate_preproc_video = FALSE, video_file_nm = NULL, timestamps_col_nm = NULL, path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "scored_detectionClusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

+Revisa los resultados finales +

+

Puedes revisar los resultados finales ahora que terminaste de +ejecutar el pipeline de ABISSMAl para detectar +clusters y generar inferencias de comportamiento.

+
scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% 
+   # Tienes que convertir las marcas de tiempo al formato POSIX cada vez que los datos se leen a R para hacer gráficas
+  dplyr::mutate(
+    start = as.POSIXct(format(as.POSIXct(start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")),
+    end = as.POSIXct(format(as.POSIXct(end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>%
+  # Ordena las marcas de tiempo
+  dplyr::arrange(-desc(start))
+
+glimpse(scored_clusters)
+
## Rows: 33
+## Columns: 20
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <chr> "exit", "entrance", "entrance", NA, NA, "entra…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:20:21.174834", "2024…
+

Cuantos eventos de entrada y salida se anotaron por día?

+

+Datos ausentes en R +

+

Para poder contar la cantidad de cada uno de estos eventos que +ABISSMAL anotó por día, necesitas saber cómo manejar datos ausentes en +R. Los datos ausentes se suelen representar usando el valor +NA (“not available” o “no disponible”), que es un tipo de +dato específico en R. Puedes determinar si un vector (o una columna en +un dataframe) contiene datos ausentes si usas la función +is.na(), que devolverá TRUE cuando encuentra +un valor de NA (un valor ausente) en el vector actual.

+
?is.na()
+
+x <- c(1, NA, 2, 3, NA)
+
+is.na(x)
+
## [1] FALSE  TRUE FALSE FALSE  TRUE
+

En el código arriba, creaste un vector que se llama x +que tiene dos valores de NA. La función +is.na() revisa si cada elemento de x es +equivalente a NA, y devuelve TRUE cuando se +cumple esa condición (o sea, cuando encuentra un dato ausente).

+

Como is.na() es una frase condicional, también puedes +usar otros símbolos especiales que son relevantes a frases +condicionales, como el símbolo de !, que sirve para +invertir una frase condicional. Por ejemplo, en el código abajo, cuando +añades ! antes de is.na(), estás preguntando +si cada elemento de x no es equivalente a +NA:

+
!is.na(x)
+
## [1]  TRUE FALSE  TRUE  TRUE FALSE
+

Como puedes ver, añadir el ! en frente del +is.na() resulta en valores binarios invertidos comparado +con usar solo is.na(), y ahora cada valor que antes era +TRUE se convirtió a FALSE. Juntos, la +habilidad de invertir la frase condicional de is.na(), +tanto como el resultado binario que devuelve is.na() son +propiedades muy útiles para encontrar y filtrar las filas de un +dataframe.

+

Por ejemplo, la función dplyr::filter() eliminará una +fila cada vez que encuentra un valor de FALSE en la columna +actual y en cambio, no eliminará una fila cada vez que encuentra un +valor de TRUE. O sea, si quieres eliminar files que +contienen valores de NA para una columna actual, añadirías +!is.na(name_of_column) adentro de +dplyr::filter(), que debería de devolver FALSE +cada vez que encuentra una fila con NA, y eliminará esa +fila como parte de la operación de filtrar.

+

+Cuenta eventos por día +

+

Ahora puedes escribir código para contar el número de eventos de +entrada y salida que ABISSMAL anotó por día. Vas a necesitar 1) crear +una columna nueva con la información sobre el día para cada marca de +tiempo, 2) eliminar filas con datos ausentes para la información anotada +de la dirección del movimiento (la columna +direction_scored, porque esta información no se puede +anotar para algunos movimientos), 3) agrupar el dataframe +por día y por la dirección anotada, y luego 4) contar el número de filas +por grupo.

+
scored_clusters %>%
+  # Extrae el día de cada marca de tiempo y crea una columna nueva con esta información
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>%
+  # Aquí estas usando la función is.na() que devolverá TRUE cuando encuentra un valor ausente (NA) en la columna actual. Con colocar el símbolo de ! antes de is.na(), estás invirtiendo la frase condicional y también el resultado, así que todos los valores TRUE se convertirán a FALSE. Por ende, dplyr::filter eliminará todas las filas que devuelven el valor de FALSE en esta expresión (o sea, todas las filas con valores ausentes en la columna direction_scored con información sobre la dirección anotada de movimiento)
+  dplyr::filter(!is.na(direction_scored)) %>%
+  # Agrupa el dataframe por ambas columnas para las cuales quieres contar filas (eventos). Aquí quieres saber el número de entradas y salidas (categorías en la columna direction_scored) por día (categorías en la columna day)
+  group_by(day, direction_scored) %>%
+  # Luego puedes resumir los datos. El número de filas aquí es el número de entradas o salidas anotadas por día
+  dplyr::summarise(
+    n = n()
+  )
+
## `summarise()` has grouped output by 'day'. You can override using the `.groups`
+## argument.
+
## # A tibble: 6 × 3
+## # Groups:   day [3]
+##     day direction_scored     n
+##   <int> <chr>            <int>
+## 1     1 entrance             4
+## 2     1 exit                 4
+## 3     2 entrance             4
+## 4     2 exit                 4
+## 5     3 entrance             4
+## 6     3 exit                 4
+

Cuatro entradas y salidas se anotaron por día. Cómo se compara este +resultado con el número de entradas y salidas que esperabas por día? Si +regresas a los tutoriales anteriores (el tercer y el cuarto tutorial) +donde creaste los datos originales simulados, deberías de poder ver que +empezaste por simular dos entradas y dos salidas por día en los datos +para el sistema de RFID y los sensores infrarrojo. Luego añadiste dos +entradas y dos salidas más por día cuando simulaste fallas de detección +del sistema de RFID (o sea, estos fueron movimientos capturados +solamente por los sensores infrarrojo). score_clusters +detectó el número correcto de entradas y salidas por día.

+

Ahora puedes revisar los eventos de posar, empezando con filtrar +todas las filas que no se anotaron como eventos de posar (o sea, filas +con valores de NA en la columna “perching_PIT_tag” que +contiene información sobre las etiquetas de PIT). Luego puedes +seleccionar solo las columnas que contienen información que es útil +revisar, como las identidades de las etiquetas PIT, y también las marcas +de tiempo para el inicio y fin de cada evento de posar.

+
scored_clusters %>%
+  # Usa una frase condicional con is.na() para retener solo las filas que tienen códigos de etiquetas PIT que fueron asociados con eventos de posar
+  dplyr::filter(!is.na(perching_PIT_tag)) %>%
+  # Luego selecciona solo las columnas que quieres revisar visualmente
+  dplyr::select(start, end, perching_PIT_tag)
+
##                 start                 end perching_PIT_tag
+## 1 2023-08-01 08:00:00 2023-08-01 08:00:02       1357aabbcc
+## 2 2023-08-01 11:30:00 2023-08-01 11:30:04       2468zzyyxx
+## 3 2023-08-02 08:00:00 2023-08-02 08:00:02       1357aabbcc
+## 4 2023-08-02 11:30:00 2023-08-02 11:30:04       2468zzyyxx
+## 5 2023-08-03 08:00:00 2023-08-03 08:00:02       1357aabbcc
+## 6 2023-08-03 11:30:00 2023-08-03 11:30:04       2468zzyyxx
+

Como aprendiste en el tercer y el cuarto tutorial, el primer evento +de posar por día se realizó por el primer individuo (con la etiqueta de +PIT “1357aabbcc”), y el segundo evento de posar por día fue realizado +por el segundo individuo (con la etiqueta de PIT “2468zzyyxx”).

+

Cuantos eventos de movimiento que no fueron eventos de posar fueron +asignados a cada individuo?

+
scored_clusters %>%
+  # Extrae el día de cada marca de tiempo y crea una columna nueva con esta información
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>%
+  # Usa una frase condicional con is.na() para retener solo las filas que tienen códigos de las etiquetas de PIT que no fueron asociados con eventos de posar
+  # Aquí estas combinando dos frases condicionales para poder buscar filas en la columna individual_initiated (o el individuo que inició el movimiento) que tienen códigos de etiquetas PIT, pero que también no tienen una etiqueta de sensor en la columna perching_sensor (o sea, eventos de movimiento que no fueron eventos de posar)
+  dplyr::filter(!is.na(individual_initiated) & is.na(perching_sensor)) %>%
+  group_by(day, individual_initiated) %>% 
+  dplyr::summarise(
+    n = n()
+  )
+
## `summarise()` has grouped output by 'day'. You can override using the `.groups`
+## argument.
+
## # A tibble: 3 × 3
+## # Groups:   day [3]
+##     day individual_initiated     n
+##   <int> <chr>                <int>
+## 1     1 1357aabbcc               4
+## 2     2 1357aabbcc               4
+## 3     3 1357aabbcc               4
+

Como puedes ver, cuatro eventos de movimiento que no fueron eventos +de posar se asignaron al primer individuo (con la etiqueta PIT +“1357aabbcc”) en cada día, que es exactamente lo que simulaste en el +tercer y el cuarto tutorial. Más movimientos que no fueron eventos de +posar se detectaron a través de estos días también, pero como esos +eventos no fueron capturados por la antena de RFID (o sea fueron fallas +simuladas de la antena de RFID), estos movimientos fueron capturados por +sólo los sensores infrarrojo que no pueden grabar información sobre la +identidad de los individuos.

+

+Construye una gráfica compleja de código de barras +

+

Ahora puedes visualizar los resultados finales. En el código abajo +vas a aprender cómo crear una gráfica de código de barras que es más +compleja de lo que viste en el tutorial anterior, pero que será más +fácil de interpretar. Para esta gráfica sería útil poder visualizar tres +tipos de inferencias de comportamiento o tipos de información a través +del tiempo: la dirección de movimiento (cuando esté disponible), la +identidad del individuo (cuando esté disponible), y los eventos de +posar.

+

Puedes empezar con construir la gráfica con añadir líneas verticales +para los eventos que no fueron eventos de posar. El color de cada línea +indicará la identidad del individuo, incluyendo cuando esta información +no estaba disponible. El tipo de línea va a contener información sobre +la dirección de movimiento, y también cuando esta información no se pudo +anotar.

+

Para poder asignar colores y tipos de líneas de la forma que quieres +en la gráfica, vas a necesitar modificar el dataframe de +los resultados finales para convertir los NAs en las dos +columnas asociadas con estos detalles de estética para convertir los +datos ausentes en información útil. Por ejemplo, cuando no hay +información sobre la identidad del individuo, sería muy útil convertir +los valores asociados de NA a un valor como “unassigned” +(“no asignado”). En el código abajo vas a usar la función +is.na() adentro de frases condicionales de +ifelse() para convertir los valores de NA +adentro de las columnas “individual_initiated” y “direction_scored” para +poder tener información útil para la gráfica que vas a hacer más +adelante.

+

Primero puedes practicar usar is.na() adentro de +ifelse() para crear un vector nuevo. En el código abajo, +vas a proveer la frase condicional que quieres probar (aquí vas a probar +si la columna que contiene las etiquetas PIT del individuo que inició el +movimiento tiene valores de NA), el valor que quieres +añadir al vector si la condición se cumple (el valor “unassigned” cuando +no hay información sobre la etiqueta PIT), y luego el valor que quieres +añadir si la condición no se cumple (en este caso es devolver la +etiqueta PIT en la columna individual_initiated si el valor actual no es +NA).

+
# is.na() devuelve TRUE cuando encuentra valores de NA adentro de un vector (o columna) y FALSE cuando el valor actual no es NA
+is.na(scored_clusters$individual_initiated)
+
##  [1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE
+## [13]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE
+## [25]  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
+
# Puedes ver que todos los valores de NA adentro de esta columna se convirtieron a "unassigned" pero todos los otros valores no cambiaron
+ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated)
+
##  [1] "unassigned" "unassigned" "unassigned" "1357aabbcc" "unassigned"
+##  [6] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" "unassigned"
+## [11] "2468zzyyxx" "unassigned" "unassigned" "unassigned" "1357aabbcc"
+## [16] "unassigned" "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc"
+## [21] "unassigned" "2468zzyyxx" "unassigned" "unassigned" "unassigned"
+## [26] "1357aabbcc" "unassigned" "1357aabbcc" "1357aabbcc" "1357aabbcc"
+## [31] "1357aabbcc" "unassigned" "2468zzyyxx"
+

Ahora puedes usar is.na() adentro de frases de +ifelse() para modificar columnas en el +dataframe.

+
scored_clusters_gg <- scored_clusters %>% 
+  dplyr::mutate(
+    # Si esta columna tiene un valor de NA, cambia el valor actual a "unassigned"
+    # Pero si el valor actual no es NA, no cambies el valor actual de esta columna
+    individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated),
+    # Repita este proceso para la columna direction_scored pero con un valor diferente (aquí "not_scored" significa que la dirección no se pudo anotar)
+    # En la frase condicional abajo añadiste is.na(perching_sensor) (después del símbolo de &) para sólo convertir los valores de NA en la columna de direction_scored si también no fueron anotados como eventos de posar en la columna perching_sensor
+    direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored)
+  ) %>%
+  # Luego vas a convertir cada una de estas columnas al tipo factor y ordenar los levels (categorías) para crear la gráfica de la forma que queremos (por ejemplo, los valores de "unassigned" y "not scored" deberían de ser los últimos valores en la leyenda)
+  dplyr::mutate(
+    individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")),
+    direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored"))
+  )
+
+# Revisa los cambios que hiciste arriba usando la función distinct() para ver que todos los valores únicos de cada columna sí fueron modificados
+# Los valores de NA en la columna de direction_scored se esperan porque se refieren a los eventos de posar
+scored_clusters_gg %>% 
+  distinct(individual_initiated, direction_scored)
+
##   individual_initiated direction_scored
+## 1           unassigned             exit
+## 2           unassigned         entrance
+## 3           1357aabbcc             <NA>
+## 4           unassigned       not scored
+## 5           1357aabbcc         entrance
+## 6           1357aabbcc             exit
+## 7           2468zzyyxx             <NA>
+

Este resultado se ve bien. Deberías de ver valores de NA +en el dataframe pero están en la columna “direction_scored” +y asociados con los eventos de posar, que vas a añadir a la gráfica en +otra capa diferente de código más adelante.

+

Puedes usar este dataframe modificado para crear la +gráfica. En el código abajo, vas a añadir líneas con colores asignados +por la identidad de individuos, y los tipos de línea van a representar +la dirección de movimiento. Primero vas a especificar los detalles +estéticos de la gráfica:

+
# Los colores están en el mismo orden que los levels (categorías) de la columna individual_initiated, así que el color naranjo va a representar la etiqueta PIT "1357aabbcc"
+levels(scored_clusters_gg$individual_initiated)
+
## [1] "1357aabbcc" "2468zzyyxx" "unassigned"
+
cols <- c("orange", "darkgreen", "black")
+
+# Los tipos de línea están en el mismo orden que los levels de la columna direction_scored, así que el valor "dotted" ("puntos") va a representar "not scored" (cuando la dirección no se pudo anotar)
+levels(scored_clusters_gg$direction_scored)
+
## [1] "entrance"   "exit"       "not scored"
+
ltys <- c("solid", "longdash", "dotted")
+

Luego puedes añadir líneas a la gráfica por la identidad de cada +individuo. Aquí estas dividiendo las llamadas de +geom_segment() por los levels (categorías o +valores únicos) en la columna individual_initiated. Solo añadiste líneas +para el primer individuo y todos los movimientos que no fueron eventos +de posar que no fueron asignados a un individuo porque después de +revisar los resultados finales, sabes que ningún movimiento (que no fue +evento de posar) fue asignado al segundo individuo.

+
ggplot() +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo
+  geom_segment(
+    data = scored_clusters_gg %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a uno de los dos individuos
+  geom_segment(
+    data = scored_clusters_gg %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Añade los tipos de línea customizados a la gráfica
+  scale_linetype_manual(values = ltys) +
+  
+  # Elimina el título del eje y
+  ylab("") +
+  
+  # Usa esta función para convertir el fondo de la gráfica a blanco y negro
+  theme_bw() +
+  
+  # Usa estas funciones de estética para eliminar el texto y los rayos del eje y
+  # Añade un argumento para cambiar la posición de la leyenda adentro de la gráfica
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  )
+

+

Para hacer esta gráfica organizaste las líneas verticales para +separar los movimientos asignados al primer individuo y los movimientos +que no fueron asignados. Esta separación vertical entre estos dos +conjuntos de datos facilita las comparaciones visuales de variación en +los patrones temporales de los movimientos. Creaste esta separación +vertical con cambiar los valores que usaste para y y +yend en la segunda capa de geom_segment() para +que esas líneas empezarían más arriba que las líneas de la primera capa +de geom_segment().

+

Puedes hacer más modificaciones que ayudarían con interpretar esta +gráfica. En primer lugar, las etiquetas de los paneles que están al lado +izquierdo se pueden cambiar para demostrar el día general de colección +de datos (como “Day 1” para el primer día) en vez de la fecha. El texto +en en eje x también se puede cambiar para demostrar sólo el tiempo (o +sea eliminar la información sobre el mes y el día), y también puedes +añadir más etiquetas (por ejemplo, una etiqueta cada media hora).

+

Puedes empezar con modificar el dataframe para añadir la +información sobre el día de colección de datos. Vas a crear esta columna +nueva con frases condicionales a través de la función +ifelse() porque sólo hay tres días de colección de datos +con etiquetas que tienes que cambiar.

+
# Crea una columna nueva en los datos originales para la fecha de colección de datos
+scored_clusters_gg2 <- scored_clusters_gg %>%
+  # Primero necesitas crear una columna con información sobre el día
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>% 
+  dplyr::mutate(
+    # Luego deberías de reemplazar la etiqueta para cada día y guardar estos resultados en una columna nueva
+    day_label = ifelse(day == 1, "Day 1", day), # Aquí el último argumento es la columna day porque la columna day_label no se ha creado todavía
+    day_label = ifelse(day == 2, "Day 2", day_label),
+    day_label = ifelse(day == 3, "Day 3", day_label)
+  )
+
+# Se ve bien
+glimpse(scored_clusters_gg2)
+
## Rows: 33
+## Columns: 22
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <fct> exit, entrance, entrance, NA, not scored, entr…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <fct> unassigned, unassigned, unassigned, 1357aabbcc…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:20:21.174834", "2024…
+## $ day                     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
+## $ day_label               <chr> "Day 1", "Day 1", "Day 1", "Day 1", "Day 1", "…
+
scored_clusters_gg2 %>% 
+  distinct(day_label)
+
##   day_label
+## 1     Day 1
+## 2     Day 2
+## 3     Day 3
+

Ahora puedes actualizar el código para incluir las etiquetas nuevas +de las fechas:

+
# Añade el dataframe como el conjunto de datos por defecto para la capa fundamental de la gráfica para que la función facet_wrap() tenga datos
+ggplot(data = scored_clusters_gg2) +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo
+  geom_segment(
+    data = scored_clusters_gg2 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a uno de los dos individuos
+  geom_segment(
+    data = scored_clusters_gg2 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Añade los tipos de línea customizados a la gráfica
+  scale_linetype_manual(values = ltys) +
+  
+  # Elimina el título del eje y
+  ylab("") +
+  
+  # Usa esta función para convertir el fondo de la gráfica a blanco y negro
+  theme_bw() +
+  
+  # Usa estas funciones de estética para eliminar el texto y los rayos del eje y
+  # Añade un argumento para cambiar la posición de la leyenda adentro de la gráfica
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  ) +
+  
+  # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas de día
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left")
+

+

Ahora que moviste la información sobre el día de colección de datos a +las etiquetas de los paneles de la gráfica, necesitas componer el texto +en el eje x. La gráfica será más fácil de interpretar si puedes alinear +las marcas de tiempo por hora y minuto para una comparación directa a +través de días diferentes.

+

Para alinear las marcas de tiempo a través de días, necesitas +actualizar el formato de las columnas que contienen las marcas de +tiempo. El código para convertir las marcas de tiempo a un formato +diferente es anidado y repetitivo pero la conversión se realizará +correctamente. Cuando le comunicas a R que deberías de convertir las +marcas de tiempo a un formato con horas, minutos, y segundos solamente, +R va a añadir un año, un mes, y un día por defecto antes de la marca de +tiempo (lo más común es que usará la fecha actual). Este comportamiento +es esperado y no es un error, más bien facilita que las marcas de tiempo +se alinean de la forma correcta a través de días (paneles) en la gráfica +(porque R considera todas las marcas de tiempo como si ocurrieron en un +solo día).

+
scored_clusters_gg3 <- scored_clusters_gg2 %>% 
+  dplyr::mutate(
+    start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")),
+    end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S"))
+  )
+
+# Deberías de ver que un año, mes, y día nuevo fueron adjuntados a las marcas de tiempo modificadas, y este resultado es esperado (ve arriba)
+glimpse(scored_clusters_gg3)
+
## Rows: 33
+## Columns: 24
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <fct> exit, entrance, entrance, NA, not scored, entr…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <fct> unassigned, unassigned, unassigned, 1357aabbcc…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:20:21.174834", "2024…
+## $ day                     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
+## $ day_label               <chr> "Day 1", "Day 1", "Day 1", "Day 1", "Day 1", "…
+## $ start_gg                <dttm> 2024-04-07 06:05:04, 2024-04-07 06:35:08, 202…
+## $ end_gg                  <dttm> 2024-04-07 06:05:05, 2024-04-07 06:35:09, 202…
+

Ahora puedes actualizar el código de crear la gráfica para cambiar la +estética del eje x usando la función scale_x_datetime() +para especificar que quieres etiquetas cada media hora en este eje. +También vas a añadir un título para el eje x y eliminar la cuadrícula en +el eje y adentro de cada panel:

+
gg <- ggplot(data = scored_clusters_gg3) +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a uno de los dos individuos
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Añade los tipos de línea customizados a la gráfica
+  scale_linetype_manual(values = ltys) +
+  
+  # Elimina el título del eje y
+  ylab("") +
+  
+  # Usa esta función para convertir el fondo de la gráfica a blanco y negro
+  theme_bw() +
+  
+  # Usa estas funciones de estética para eliminar el texto y los rayos del eje y
+  # Añade un argumento para cambiar la posición de la leyenda adentro de la gráfica
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  ) +
+  
+  # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas de día
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left") +
+  
+  # Cambia la estética de las etiquetas del eje x
+  scale_x_datetime(
+    date_breaks = "30 mins",
+    date_labels = "%H:%M"
+  ) +
+  
+  # Añade un título para el eje x
+  xlab("Time of day (HH:MM)") +
+  
+  # Puedes quitar la cuadrícula en el eje y (mayor y menor) adentro de cada panel
+  theme(
+    panel.grid.major.y = element_blank(),
+    panel.grid.minor.y = element_blank()
+  ) 
+
+gg
+

+

Todavía falta un tipo de información importante que necesitas añadir +a esta gráfica: los eventos de posar. Vas a añadir esta información con +otra capa de geom_segment() pero ahora vas a crear líneas +cortas y anchas para que aparezcan más como puntos en la gráfica:

+
gg <- gg + 
+  
+  # Añade los eventos de posar como líneas con orillas redondeadas, y puedes añadir colores que indican la identidad del individuo a través de la columna individual_initiated
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(!is.na(perching_sensor)),
+    aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated),
+    linewidth = 2, lineend = "round"
+  ) +
+  
+  # Añade los colores customizados para estos eventos de posar. Los colores también aplican a las líneas de movimientos por individuo que no fueron eventos de posar que añadiste en capas anteriores de geom_segment()
+  scale_color_manual(values = cols)
+
+gg
+

+

Ahora deberías de ver que una leyenda de color aparece arriba de la +gráfica con la adición de esta capa adicional de +geom_segment(). Ambas de las leyendas se pueden mejorar. +Puedes modificar la gráfica con actualizar el título de cada leyenda, +aumentar el tamaño de texto de cada leyenda, y reducir el espacio blanco +entre las leyendas y la gráfica. Para modificar los títulos de cada +leyenda, vas a usar las funciones guides() y +guide_legend(). Para aumentar el tamaño de texto en la +leyenda, vas a usar el argumento legend.text adentro de la +función theme(), y para reducir el espacio blanco entre la +gráfica y las leyendas, vas a usar el argumento +legend.margin adentro de la función +theme().

+
# Ve más información sobre la función que controla los margenes (espacio blanco) alrededor de la leyenda
+?margin
+
+gg <- gg +
+  
+  # Aumenta el tamaño de texto de cada leyenda y reduce el espacio blanco entre la gráfica y las leyendas
+  theme(
+    legend.text = element_text(size = 10),
+    legend.margin = margin(-1, -1, -1, -1, unit = "pt")
+  ) +
+
+  # Modifica los títulos de cada leyenda
+  guides(
+    linetype = guide_legend(title = "Direction"),
+    color = guide_legend(title = "Individual")
+  )
+
+gg
+

+

Finalmente puedes guardar esta gráfica como un archivo:

+
gg
+

+
# Guarda el archivo con la gráfica en tu computadora
+ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300)
+

Puedes continuar con modificar estéticas menores a este archivo para +crear una figura de alta calidad para una publicación. Por ejemplo, +puedes cambiar el tamaño final de la imagen (width, +height), tanto como la resolución (dpi). +Puedes también cambiar el tamaño de texto en cada eje y los títulos de +cada eje, o la posición de la leyenda mientras determinas el tamaño +final de la imagen en el archivo.

+

Abajo está todo el código que escribiste para la gráfica final, en +una forma más condensada y reorganizada:

+
ggplot(data = scored_clusters_gg3) +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que fue asignado al primer individuo
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Añade una línea vertical para cada evento que no fue un evento de posar y que no fue asignado a uno de los dos individuos
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Añade los eventos de posar como líneas con orillas redondeadas, y puedes añadir colores que indican la identidad del individuo a través de la columna individual_initiated
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(!is.na(perching_sensor)),
+    aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated),
+    linewidth = 2, lineend = "round"
+  ) +
+  
+  # Añade los tipos de línea customizados a la gráfica
+  scale_linetype_manual(values = ltys) +
+  
+  # Añade los colores customizados para estos eventos de posar. Los colores también aplican a las líneas de movimientos por individuo que no fueron eventos de posar que añadiste en capas anteriores de geom_segment()
+  scale_color_manual(values = cols) +
+  
+  # Modifica los títulos de cada leyenda
+  guides(
+    linetype = guide_legend(title = "Direction"),
+    color = guide_legend(title = "Individual")
+  ) +
+  
+  # Añade un título para el eje x
+  xlab("Time of day (HH:MM)") +
+  
+  # Elimina el título del eje y
+  ylab("") +
+  
+  # Cambia la estética de las etiquetas del eje x
+  scale_x_datetime(
+    date_breaks = "30 mins",
+    date_labels = "%H:%M"
+  ) +
+  
+  # Crea paneles en la gráfica por día, aquí usarás las etiquetas nuevas de día
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left") +
+  
+  # Usa esta función para convertir el fondo de la gráfica a blanco y negro
+  theme_bw() +
+  
+  # Usa estas funciones de estética para eliminar el texto y los rayos del eje y
+  # Añade un argumento para cambiar la posición de la leyenda adentro de la gráfica
+  # Puedes quitar la cuadrícula en el eje y (mayor y menor) adentro de cada panel
+  # Aumenta el tamaño de texto de cada leyenda y reduce el espacio blanco entre la gráfica y las leyendas
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top",
+    panel.grid.major.y = element_blank(),
+    panel.grid.minor.y = element_blank(),
+    legend.text = element_text(size = 10),
+    legend.margin = margin(-1, -1, -1, -1, unit = "pt")
+  )
+

Acabas de completar el tutorial final del pipeline de +procesar y analizar datos de ABISSMAL. También practicaste tus +habilidades de programar y tus habilidades de la ciencia de datos en un +contexto biológico. Muy bien hecho! Nos ayudaría mucho si puedes +completar la forma de Google para una evaluación de estos tutoriales ya +que los hayas terminado. Tus respuestas nos ayudarán mejorar estos +tutoriales en el futuro. Un enlace a la forma de Google estará +disponible en el archivo README para los tutoriales.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_01_Introduction.Rmd b/R/vignettes/Vignette_01_Introduction.Rmd new file mode 100644 index 0000000..b5af88b --- /dev/null +++ b/R/vignettes/Vignette_01_Introduction.Rmd @@ -0,0 +1,116 @@ +--- +title: "Vignette 01: Introduction" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +

Vignette overview and learning objectives

+ +This first vignette will take you through a brief introduction to RStudio, downloading a local version of the ABISSMAL GitHub repository, and an introduction to the data analysis workflow provided by ABISSMAL. Through this tutorial, you will learn: + +1. How to configure your RStudio session +2. How to create a local version of a GitHub repository +3. The data processing and analysis steps provided by the ABISSMAL R functions +4. How to troubleshoot errors online +5. How to report bugs as GitHub issues + +There are many different ways to carry out a single task or to solve a single problem in R. Keep this in mind throughout all of these tutorials. In each tutorial, you will learn examples of how to carry out specific tasks or solve specific problems with code, but these examples are not an exhaustive overview of how to address any one task or solve any one problem. Instead, use these tutorials as an opportunity to practice using your coding skills in a biological context. + +

Configure your RStudio session

+ +If you are new to using R (a programming language for statistical analysis) and RStudio (a graphical user interface for R), then I recommend checking out the introductory lesson [R for Reproducible Scientific Analysis](https://swcarpentry.github.io/r-novice-gapminder/) provided by the [Software Carpentry](https://software-carpentry.org/). + +Once you have R and RStudio installed on your computer, you can open RStudio by clicking on the software icon. The default pane configuration for RStudio should look like this (the background color will probably be different): + +
+![A screenshot of the default RStudio pane configuration, with the console pane below the source pane](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_01.png) + +This pane layout makes it difficult to see the code that you're writing and saving in a physical file in the source pane, and the output of that code in the console pane. You can reconfigure the panes so that the source and console panes are horizontally adjacent to one another, which makes it easier to immediately check the output of any code that you run. To reconfigure the pane layout: + +* Go to the menu along the top bar of the RStudio window and select the option "Tools" + +* Select "Global Options" in the pop-up menu + +* In the next pop-up menu, select the option "Pane Layout" along the lefthand side + +* Use the dropdown menus to select "Source" as the top right pane and "Console" as the top left pane + +* You can select "Apply" to apply those changes, then "Ok" to exit this window + +The RStudio pane layout should now look like this: + +
+![A screenshot of the updated RStudio pane configuration, with the console pane to the right of the source pane](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/RStudio_02.png) + +Another useful change to the RStudio configuration is soft-wrapping lines of text and code so that you don't have to scroll horizontally to see long lines of text in your Source pane. To do this, go to "Tools", then "Global Options", then "Code", and check the box next to the option "Soft-wrap R source files", then select "Apply" and "Ok". + +You can also change the font size, font color, and background color of your RStudio workspace. After selecting "Tools" and "Global Options", go to "Appearance" to see some different options. + +

Create a local version of an existing GitHub repository

+ +If you are new to using GitHub, then I recommend downloading [GitHub Desktop](https://desktop.github.com/). This software is a graphical user interface for the GitHub version control platform. We will work through how to download the GitHub repository for ABISSMAL so that you can access the ABISSMAL R functions on your local computer. If you are already familiar with GitHub and using Git through the command line, then check out the installation instructions on the [ABISSMAL README](https://github.com/lastralab/ABISSMAL). + +Once you have installed GitHub Desktop: + +* Open GitHub Desktop by clicking on the icon + +* Open your default Internet browser and navigate to the ABISSMAL GitHub repository: https://github.com/lastralab/ABISSMAL + +* Click on the green "Code" button and copy the web URL under the HTTPS option in the dropdown menu + +* In the GitHub Desktop window, go to the top menu and select "File" + +* Select "Clone repository", + +* Select the tab "URL" + +* Paste the web URL for ABISSMAL into the text box under "Repository URL or GitHub username and repository" + +* Check that the directory in the text box under "Local path" is the correct place to install the ABISSMAL GitHub repository on your computer. For instance, if "Local path" is "/home/User/Desktop/ABISSMAL", then the ABISSMAL repository will be installed directly to your Desktop + +* Select "Clone" once you're ready to create a local version of the remote ABISSMAL repository on your computer + +Once the repository has been installed on your computer, you should see the following directory and file structure inside the folder labeled "ABISSMAL" that looks similar to this: + +
+![A screenshot of the directory that is the local ABISSMAL repository](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/ABISSMAL_localrepo.png) + +The files that we'll be working with throughout the following vignettes are inside of the "R" folder. This folder contains 6 R files (extension ".R"), a README file that contains more information about each R file, and folders with the vignettes in RMarkdown format (extension ".Rmd") as well as automated unit testing scripts. + +

ABISSMAL data processing and analysis

+ +ABISSMAL provides 5 different R functions for data processing and analysis. These functions are briefly described below in the order in which they should generally be used. The functions are also described in more detail in the R folder README as well as the accompanying methods manuscript: + +1. `combine_raw_data` will automatically concatenate spreadsheets of raw data collected each day into a single spreadsheet across days. This data concatenation is performed for a given sensor type (e.g. infrared beam breakers or an infrared video camera) and does not change or overwrite the original raw data. This function can use data from any sensor type (RFID, infrared beam breakers, video camera, or temperature probe) as input, but the following functions do not accept data from the temperature probe + +2. `detect_perching_events` uses the output of `combine_raw_data` as input, and will detect stretches of detections that occurred close together in time as perching events. These events represent periods of time when an individual was perched in the RFID antenna in the nest container entrance. This function returns a spreadsheet of the timing of inferred perching events detected using data from sensors placed around the nest container entrance (RFID or infrared beam breakers) + +3. `preprocess_detections` uses the output of `combine_raw_data` as input, and removes detections that occurred very close together in time. This function returns a spreadsheet of pre-processed data per sensor type, in which consecutive detections are all separated by no less than a given temporal threshold (e.g. when using a threshold of 1 second, only a single detection can occur per second) + +4. `detect_clusters` uses the output of `preprocess_detections` from any 2 or more movement sensor types as input. This function identifies detections across 2+ sensor types that occurred close together in time, and returns temporal information and metadata about each detection cluster. Detection clusters represent discrete movement events by one or more individuals + +5. `score_clusters` uses the output of `detect_clusters` as the main input. This function can also use the output of `detect_perching_events` to integrate perching events detected in the raw RFID or infrared beam breaker datasets. It can also use the output of `preprocess_detections` to integrate video recording events that were not detected by `detect_clusters`. This function serves to make behavioral inferences about each movement event, including the direction of movement, the magnitude of movement, individual identity when RFID data was present in a cluster, and where the beginning of the movement event likely occurred (at the container entrance or inside of the container). This function returns a spreadsheet of behavioral inferences and other metadata about each movement event that can be used for subsequent analyses and visualization + +
+![Figure 4 from the ABISSMAL methods manuscript under review, that shows a general workflow across these 5 functions](/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/images/Figure4_ComputationalAnalyses.png) +* Figure from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. Hobson. 2023. Automated tracking of avian parental care. [EcoEvoRxiv preprint]((https://ecoevorxiv.org/repository/view/6268/)). + +

Troubleshoot errors online

+ +You will run into errors while writing and running code, and while this can be frustrating, errors are an important part of the overall learning experience. Errors can arise for many different reasons, including typos that you introduced while writing code, issues with package versions that you installed, or perhaps missing packages. Sometimes, errors may be due to issues with the custom ABISSMAL functions that you will use in the subsequent vignettes, or issues with the code in the vignettes themselves. + +Whenever you run into an error with your code, it's important to troubleshoot the error and figure out out how to solve it before assuming that the error is due to the custom ABISSMAL functions or issues with the ABISSMAL vignettes. There are many different resources online that you can use to troubleshoot common errors in R. One way to start troubleshooting errors online is to copy and paste the error message printed to your console into a search engine, which should point you to public forums online in which people have asked about and solved similar errors. You may also be able to use generative AI tools like ChatGPT to search for potential typos or issues with your code. You can also read R documentation to figure out if the error is associated with a specific package or function upon which ABISSMAL depends. + +Once you have thoroughly researched a given error, and you're sure that the error you're getting isn't due to a typo or issues with data structure on your end, then it's time to consider whether the error is due to a bug in a custom ABISSMAL function or the code provided in a vignette. These are bugs that you can report to the ABISSMAL developers on GitHub (see below). + +

Report bugs on GitHub

+ +As you work through each vignette, you may encounter "bugs" or errors with the code, including code that does not work or that yields incorrect outcomes. These bugs can occur with the ABISSMAL functions themselves, or with the code in a given vignette. When you encounter a bug with an ABISSMAL function, you can create an Issue through the GitHub repository. To create a new Issue, you can select "New Issue" on the [Issues page](https://github.com/lastralab/ABISSMAL/issues) of the main ABISSMAL repository, then follow the instructions in the Issue template to add the information needed for the code developers to address the bug effectively. You can also add the tag "bug" to your issue. If you encounter an issue with the ABISSMAL vignettes, then you can submit an issue through the main ABISSMAL GitHub repository as well, as long as you clarify that the bug is related to code in a given vignette. + +In the next vignette, we will create simulated datasets of raw movement data to learn how to use the different ABISSMAL functions described above. \ No newline at end of file diff --git a/R/vignettes/Vignette_01_Introduction.html b/R/vignettes/Vignette_01_Introduction.html new file mode 100644 index 0000000..d77d612 --- /dev/null +++ b/R/vignettes/Vignette_01_Introduction.html @@ -0,0 +1,1828 @@ + + + + + + + + + + + + + + + +Vignette 01: Introduction + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

This first vignette will take you through a brief introduction to +RStudio, downloading a local version of the ABISSMAL GitHub repository, +and an introduction to the data analysis workflow provided by ABISSMAL. +Through this tutorial, you will learn:

+
    +
  1. How to configure your RStudio session
  2. +
  3. How to create a local version of a GitHub repository
  4. +
  5. The data processing and analysis steps provided by the ABISSMAL R +functions
  6. +
  7. How to troubleshoot errors online
  8. +
  9. How to report bugs as GitHub issues
  10. +
+

There are many different ways to carry out a single task or to solve +a single problem in R. Keep this in mind throughout all of these +tutorials. In each tutorial, you will learn examples of how to carry out +specific tasks or solve specific problems with code, but these examples +are not an exhaustive overview of how to address any one task or solve +any one problem. Instead, use these tutorials as an opportunity to +practice using your coding skills in a biological context.

+

+Configure your RStudio session +

+

If you are new to using R (a programming language for statistical +analysis) and RStudio (a graphical user interface for R), then I +recommend checking out the introductory lesson R for +Reproducible Scientific Analysis provided by the Software Carpentry.

+

Once you have R and RStudio installed on your computer, you can open +RStudio by clicking on the software icon. The default pane configuration +for RStudio should look like this (the background color will probably be +different):

+


A screenshot of the default RStudio pane configuration, with the console pane below the source pane

+

This pane layout makes it difficult to see the code that you’re +writing and saving in a physical file in the source pane, and the output +of that code in the console pane. You can reconfigure the panes so that +the source and console panes are horizontally adjacent to one another, +which makes it easier to immediately check the output of any code that +you run. To reconfigure the pane layout:

+
    +
  • Go to the menu along the top bar of the RStudio window and select +the option “Tools”

  • +
  • Select “Global Options” in the pop-up menu

  • +
  • In the next pop-up menu, select the option “Pane Layout” along +the lefthand side

  • +
  • Use the dropdown menus to select “Source” as the top right pane +and “Console” as the top left pane

  • +
  • You can select “Apply” to apply those changes, then “Ok” to exit +this window

  • +
+

The RStudio pane layout should now look like this:

+


A screenshot of the updated RStudio pane configuration, with the console pane to the right of the source pane

+

Another useful change to the RStudio configuration is soft-wrapping +lines of text and code so that you don’t have to scroll horizontally to +see long lines of text in your Source pane. To do this, go to “Tools”, +then “Global Options”, then “Code”, and check the box next to the option +“Soft-wrap R source files”, then select “Apply” and “Ok”.

+

You can also change the font size, font color, and background color +of your RStudio workspace. After selecting “Tools” and “Global Options”, +go to “Appearance” to see some different options.

+

+Create a local version of an existing GitHub repository +

+

If you are new to using GitHub, then I recommend downloading GitHub Desktop. This software is +a graphical user interface for the GitHub version control platform. We +will work through how to download the GitHub repository for ABISSMAL so +that you can access the ABISSMAL R functions on your local computer. If +you are already familiar with GitHub and using Git through the command +line, then check out the installation instructions on the ABISSMAL README.

+

Once you have installed GitHub Desktop:

+
    +
  • Open GitHub Desktop by clicking on the icon

  • +
  • Open your default Internet browser and navigate to the ABISSMAL +GitHub repository: https://github.com/lastralab/ABISSMAL

  • +
  • Click on the green “Code” button and copy the web URL under the +HTTPS option in the dropdown menu

  • +
  • In the GitHub Desktop window, go to the top menu and select +“File”

  • +
  • Select “Clone repository”,

  • +
  • Select the tab “URL”

  • +
  • Paste the web URL for ABISSMAL into the text box under +“Repository URL or GitHub username and repository”

  • +
  • Check that the directory in the text box under “Local path” is +the correct place to install the ABISSMAL GitHub repository on your +computer. For instance, if “Local path” is +“/home/User/Desktop/ABISSMAL”, then the ABISSMAL repository will be +installed directly to your Desktop

  • +
  • Select “Clone” once you’re ready to create a local version of the +remote ABISSMAL repository on your computer

  • +
+

Once the repository has been installed on your computer, you should +see the following directory and file structure inside the folder labeled +“ABISSMAL” that looks similar to this:

+


A screenshot of the directory that is the local ABISSMAL repository

+

The files that we’ll be working with throughout the following +vignettes are inside of the “R” folder. This folder contains 6 R files +(extension “.R”), a README file that contains more information about +each R file, and folders with the vignettes in RMarkdown format +(extension “.Rmd”) as well as automated unit testing scripts.

+

+ABISSMAL data processing and analysis +

+

ABISSMAL provides 5 different R functions for data processing and +analysis. These functions are briefly described below in the order in +which they should generally be used. The functions are also described in +more detail in the R folder README as well as the accompanying methods +manuscript:

+
    +
  1. combine_raw_data will automatically concatenate +spreadsheets of raw data collected each day into a single spreadsheet +across days. This data concatenation is performed for a given sensor +type (e.g. infrared beam breakers or an infrared video camera) and does +not change or overwrite the original raw data. This function can use +data from any sensor type (RFID, infrared beam breakers, video camera, +or temperature probe) as input, but the following functions do not +accept data from the temperature probe

  2. +
  3. detect_perching_events uses the output of +combine_raw_data as input, and will detect stretches of +detections that occurred close together in time as perching events. +These events represent periods of time when an individual was perched in +the RFID antenna in the nest container entrance. This function returns a +spreadsheet of the timing of inferred perching events detected using +data from sensors placed around the nest container entrance (RFID or +infrared beam breakers)

  4. +
  5. preprocess_detections uses the output of +combine_raw_data as input, and removes detections that +occurred very close together in time. This function returns a +spreadsheet of pre-processed data per sensor type, in which consecutive +detections are all separated by no less than a given temporal threshold +(e.g. when using a threshold of 1 second, only a single detection can +occur per second)

  6. +
  7. detect_clusters uses the output of +preprocess_detections from any 2 or more movement sensor +types as input. This function identifies detections across 2+ sensor +types that occurred close together in time, and returns temporal +information and metadata about each detection cluster. Detection +clusters represent discrete movement events by one or more +individuals

  8. +
  9. score_clusters uses the output of +detect_clusters as the main input. This function can also +use the output of detect_perching_events to integrate +perching events detected in the raw RFID or infrared beam breaker +datasets. It can also use the output of +preprocess_detections to integrate video recording events +that were not detected by detect_clusters. This function +serves to make behavioral inferences about each movement event, +including the direction of movement, the magnitude of movement, +individual identity when RFID data was present in a cluster, and where +the beginning of the movement event likely occurred (at the container +entrance or inside of the container). This function returns a +spreadsheet of behavioral inferences and other metadata about each +movement event that can be used for subsequent analyses and +visualization

  10. +
+


Figure 4 from the ABISSMAL methods manuscript under review, that shows a general workflow across these 5 functions +* Figure from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A. +Hobson. 2023. Automated tracking of avian parental care. EcoEvoRxiv +preprint.

+

+Troubleshoot errors online +

+

You will run into errors while writing and running code, and while +this can be frustrating, errors are an important part of the overall +learning experience. Errors can arise for many different reasons, +including typos that you introduced while writing code, issues with +package versions that you installed, or perhaps missing packages. +Sometimes, errors may be due to issues with the custom ABISSMAL +functions that you will use in the subsequent vignettes, or issues with +the code in the vignettes themselves.

+

Whenever you run into an error with your code, it’s important to +troubleshoot the error and figure out out how to solve it before +assuming that the error is due to the custom ABISSMAL functions or +issues with the ABISSMAL vignettes. There are many different resources +online that you can use to troubleshoot common errors in R. One way to +start troubleshooting errors online is to copy and paste the error +message printed to your console into a search engine, which should point +you to public forums online in which people have asked about and solved +similar errors. You may also be able to use generative AI tools like +ChatGPT to search for potential typos or issues with your code. You can +also read R documentation to figure out if the error is associated with +a specific package or function upon which ABISSMAL depends.

+

Once you have thoroughly researched a given error, and you’re sure +that the error you’re getting isn’t due to a typo or issues with data +structure on your end, then it’s time to consider whether the error is +due to a bug in a custom ABISSMAL function or the code provided in a +vignette. These are bugs that you can report to the ABISSMAL developers +on GitHub (see below).

+

+Report bugs on GitHub +

+

As you work through each vignette, you may encounter “bugs” or errors +with the code, including code that does not work or that yields +incorrect outcomes. These bugs can occur with the ABISSMAL functions +themselves, or with the code in a given vignette. When you encounter a +bug with an ABISSMAL function, you can create an Issue through the +GitHub repository. To create a new Issue, you can select “New Issue” on +the Issues +page of the main ABISSMAL repository, then follow the instructions +in the Issue template to add the information needed for the code +developers to address the bug effectively. You can also add the tag +“bug” to your issue. If you encounter an issue with the ABISSMAL +vignettes, then you can submit an issue through the main ABISSMAL GitHub +repository as well, as long as you clarify that the bug is related to +code in a given vignette.

+

In the next vignette, we will create simulated datasets of raw +movement data to learn how to use the different ABISSMAL functions +described above.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_02_Setup.Rmd b/R/vignettes/Vignette_02_Setup.Rmd new file mode 100644 index 0000000..fbe8ea7 --- /dev/null +++ b/R/vignettes/Vignette_02_Setup.Rmd @@ -0,0 +1,186 @@ +--- +title: "Vignette 02: Setup" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = FALSE) + +``` + +

Vignette overview and learning objectives

+ +In this second vignette, we will set up a virtual workspace for coding sessions with the ABISSMAL R functions. You will learn basic R programming skills and open science tips for writing code, including: + +1. How to use RMarkdown files +2. Cleaning your global environment +3. Running code inside of an RMarkdown chunk +4. Learning about R functions and documentation +5. Installing and loading packages +6. Commenting code +7. Coding shortcuts in RStudio +8. Creating and using a working directory + +

Using RMarkdown files

+ +Each vignette in this series is available as an RMarkdown file (extension .Rmd) and as an HTML file that can be viewed in your default Internet browser. Each HTML file was generated by knitting the output of an RMarkdown file. You can check out the [RMarkdown documentation](https://rmarkdown.rstudio.com/lesson-1.html) for more information about how to use this file format to write code and knit the code output into reports. + +RMarkdown files facilitate sharing your code and the output of your code with others. If you're new to using RMarkdown, the best way to work through these vignettes will be to create a new RMarkdown file for each vignette and write out the code from each vignette yourself. You will get the most coding practice out of these vignettes if you write out the code and comments in your own words (outside and inside of chunks). You can open the knitted output for each vignette in your Internet browser as a guide while you write code in your own RMarkdown file. Or you can open the vignette RMarkdown file and your own RMarkdown file side by side in RStudio by adding a third column in the pane layout: + +* Tools +* Global Options +* Pane Layout +* Select the Add Column option to add another Source pane in a third column +* Open the original vignette RMarkdown file in one source pane, and your own RMarkdown file in the other source pane + +If you're already familiar with writing R code and using RMarkdown files, then you can open the original RMarkdown file for each vignette and run the code inside each file as you work through each vignette (with some modification of paths to reflect folders and files on your local computer). If you want to preserve the original code in each RMarkdown file, you can create a copy of each vignette and modify the copies instead. + +

Clean your global environment

+ +Your global environment is your virtual workspace in R, and can hold different packages and objects to facilitate your data analysis and programming objectives. You can see the different packages and objects currently loaded in your environment by clicking on the tab "Environment" in the pane that also includes the "History" and "Connections" tab. + +If you started a new RStudio session then your global environment will likely be empty. But if you're working in an older session or maybe in an RProject workspace, then your global environment will likely have packages and objects already loaded. Cleaning your global environment can be good practice to ensure that you're starting a new coding session with a fresh workspace. If you don't clean your global environment, even when using the same code between sessions, then you risk using old versions of objects that don't reflect your newest changes to the code. + +You can clean your global environment directly from the RStudio interface by clicking on the broom icon below the "Environment" tab (hit "Yes" with "hidden objects" selected). + +You can also clean your global environment by running the code below. You can run the code inside this chunk in different ways: + +* Click on the small green arrow icon on the top right side of the chunk to run the code in this chunk only + +* Place your cursor anywhere within the line of code, then go up to the "Run" icon on the top right of the Source pane that has a white square and green arrow. In the dropdown menu that appears, select "Run Selected Lines" or "Run Current Chunk" (see the keyboard shortcuts next to each) + +* Place your cursor anywhere within the line of code and use the keyboard shortcut Ctrl + Enter to run the current line of code + +* Place your cursor anywhere within the line of code and use the keyboard shortcut Ctrl + Shift + Enter to run all code in the current chunk + +The first keyboard shortcut to run one line of code at a time can be helpful to see the output of each line of code at a time and check for errors. +```{r} + +rm(list = ls()) + +``` + +The code above to clean your global environment is a nested expression with 2 functions: `rm()` and `ls()`. The `()` notation is used for functions in R. Functions are operations that you can apply in your own code by using the name of a specific function. R has a set of base functions that you can access without loading any additional packages, including the two functions above (`rm()` and `ls()`). + +

Access R function documentation

+ +You can access the documentation for these functions by clicking on the "Help" tab in the pane that also has "Files" and "Plots", and typing the name of the function in the search bar. You can also access function documentation by running the following code: +```{r} + +?rm + +``` + +```{r} + +?ls + +``` + +For each function, the documentation holds specific sections that can be useful for understanding how the function works, especially the function's arguments. A function's arguments are the values that it expects the user to provide in order to guide or customize the operation. Many functions will have preset default values for some arguments that will be used when you don't provide specific values for arguments. + +For instance, the function `rm()` has an argument called `list` (followed by an `=`, which is used to pass a specific value to the argument). In order for `rm()` to clean your global environment in the way that you want, you need to provide information after the argument `list`. To remove everything in your global environment, we're using the output of the function `ls()`, which is a function that lists the names of all of the objects in your global environment. + +

Install and load packages

+ +After cleaning the global environment, you still need to set up your virtual workspace for the upcoming coding session. One important step here is to make sure you have access to functions that you need but which aren't accessible through the base R functions. For instance, [the `tidyverse`](https://www.tidyverse.org/) is a set of R packages that provide useful functions and expressions for data science purposes. + +If you don't already have tidyverse installed on your local computer, then you need to install the tidyverse in order to access these functions. The code below installs the tidyverse from [CRAN](https://cran.r-project.org/), the Comprehensive R Archive Network that holds thousands of R packages online. +```{r} + +# Install the tidyverse from CRAN +install.packages("tidyverse") + +``` + +In the chunk above, I added a comment using the `#` symbol. Any text that you write after a `#` symbol will be treated as text and will not be run as code. It's good practice to comment your code, especially as you're learning more about how to code in R. For biologists, continuing to comment your code even when you're a coding expert can also be good practice. Commenting and documenting your work makes the code that you publish with manuscripts or software tools more accessible to others in the community. + +Once you've installed the tidyverse on your computer, you do not need to install it again. But you do need to use the function `library()` to load the package into your global environment in order to access functions that are held inside of the tidyverse packages: +```{r} + +library(tidyverse) + +``` + +

Coding shortcuts

+ +RStudio has a number of useful keyboard shortcuts for writing code. You can find these shortcuts by going to Tools at the header of the RStudio window, then Keyboard Shortcuts Help, which will pull up a popup window with all of the default keyboard shortcuts. You already learned some keyboard shortcuts above about how to run code inside of an RMarkdown chunk. Some additional useful keyboard shortcuts are "Shift + Ctrl + C", which can be used to comment one or multiple lines of code or create new comments, and "Ctrl + Alt + I", which automatically creates a new RMarkdown chunk. + +Another useful type of shortcut provided by RStudio is tab completion of code. For instance, in the chunk below, after writing `libr` and pressing Tab, you should see a small popup window appear that lists all of the available functions, packages, or objects that start with the pattern "libr". You can use the arrow keys to select the option that you'd like and hit Enter to complete the line (for instance, to write `library()` for loading a package). +```{r eval = FALSE} + +libr + +``` + +

Troubleshooting incomplete statements

+ +As you write and run code in RStudio, it's important to keep an eye on the console. When you see the symbol `>` in the console, this means that the console finished running code and is ready for another operation. However, when you see the symbol `+` in the console, this means that the statement you just ran was incomplete. Incomplete lines of code can occur when you have a typo so that a statement isn't fully closed, like when you miss an opening or closing parenthesis in a function. Below is an example of an incomplete statement: +```{r eval = FALSE} + +library(tidyverse + +``` + +When you run the code above, you should see a `+` symbol appear in the console. This is because you're missing a parenthesis to close the function `library()`. There are two ways to solve this problem. First, if you know what is missing from the statement, you can type the missing bit into the console and hit "Enter". Or, you can click in the console, and then press "Esc", which will remove the incomplete statement and restart the console for additional code. It's good practice to keep an eye on the console to check that the code you're running is producing output. If you run a lot of code and don't see the output that you expect, it may be because you included an incomplete statement, and it would be better to clean the console and check your code before running it again. + +

Create your working directory

+ +The next important step for setting up your virtual workspace is to decide on the working directory that you will use for the coding session. A directory is a location (a folder) on your computer where R will search for files to read in. When you write out files from R, those files can also be written to your working directory. + +You can use the function `getwd()` to check your current working directory. +```{r eval = TRUE} + +getwd() + +``` + +My working directory is set by default to the folder on my computer where I saved this RMarkdown file. To work in a separate directory that holds only data generated from these vignettes, you can create a new directory or folder on your computer: +```{r} + +?dir.create + +dir.create("/home/gsvidaurre/Desktop/ABISSMAL_vignettes") + +``` + +In the code above, you'll need to replace the path (or the list of folder names) to reflect where you want to save the new folder called `ABISSMAL_vignettes` (or another name of your choice) on your own computer. For those using Windows, if you copy and paste a path into R directly from your computer then you may need to change the direction of the slashes. + +In introductory R coding courses, it's common to formally set your working directory before a coding session by using the function `setwd()`. It's good practice to avoid using `setwd()` in code that that you want to share with collaborators or share more widely in the spirit of open science (e.g. accompanying a publication or a new tool). When you use `setwd()` and share the code with others, you're assuming that someone else has the same working directory on their computer, which generally will not be true. There are other ways in which you can specify your working directory throughout your code without using `setwd()`. + +For instance, let's say that we want to make a copy of the first vignette and then save that copy inside of the directory that we created above. We can use a base R functions to copy and save the file to the correct working directory. You will need to update the paths below to reflect the folder where the vignettes are saved on your computer, as well as your own working directory: +```{r} + +file.copy( + from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Vignette_01_Introduction.Rmd", + to = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd" + ) + +``` + +In the code above, we're specifying 2 arguments to the function `file.copy()`. The first argument specifies the location of the file that we want to copy (/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/) and the name of the file (Vignette_01_Introduction.Rmd). The second argument specifies the location where we want to save this copy (/home/gsvidaurre/Desktop/ABISSMAL_vignettes/), and a new file name for the copied file (Vignette_01_Introduction_copy.Rmd). In each argument, the file paths are surrounded by 2 double quotes, in order to indicate that the text inside each pair of quotes should be considered character information or a "character string" (a formal term for text included in R code). + +When you run the line of code above, the output in the console should read "[1] TRUE" if the file was successfully copied and saved. You can check that the function worked correctly by opening a file window on your computer in this new directory. You can also use the base R function `list.files()` to check whether the copied file exists in the working directory. The output of `list.files()` is a list of all of the files contained within the new directory, and that list should contain a single file: "Vignette_01_Introduction_copy.Rmd". +```{r} + +list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes") + +``` + +The code above is an example of how you can specify your working directory in the code that you write without needing to use `setwd()`. In the following vignettes, you will see other examples of how to specify your working directory without `setwd()`. Before moving on to the next vignette, you can remove the file that you copied to your working directory: + +```{r} + +file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd") + +``` + +In the next vignette, you will start creating and manipulating objects in R that can be used in the ABISSMAL data processing and analysis pipeline. \ No newline at end of file diff --git a/R/vignettes/Vignette_02_Setup.html b/R/vignettes/Vignette_02_Setup.html new file mode 100644 index 0000000..00799ef --- /dev/null +++ b/R/vignettes/Vignette_02_Setup.html @@ -0,0 +1,1896 @@ + + + + + + + + + + + + + + + +Vignette 02: Setup + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this second vignette, we will set up a virtual workspace for +coding sessions with the ABISSMAL R functions. You will learn basic R +programming skills and open science tips for writing code, +including:

+
    +
  1. How to use RMarkdown files
  2. +
  3. Cleaning your global environment
  4. +
  5. Running code inside of an RMarkdown chunk
  6. +
  7. Learning about R functions and documentation
  8. +
  9. Installing and loading packages
  10. +
  11. Commenting code
  12. +
  13. Coding shortcuts in RStudio
  14. +
  15. Creating and using a working directory
  16. +
+

+Using RMarkdown files +

+

Each vignette in this series is available as an RMarkdown file +(extension .Rmd) and as an HTML file that can be viewed in your default +Internet browser. Each HTML file was generated by knitting the output of +an RMarkdown file. You can check out the RMarkdown +documentation for more information about how to use this file format +to write code and knit the code output into reports.

+

RMarkdown files facilitate sharing your code and the output of your +code with others. If you’re new to using RMarkdown, the best way to work +through these vignettes will be to create a new RMarkdown file for each +vignette and write out the code from each vignette yourself. You will +get the most coding practice out of these vignettes if you write out the +code and comments in your own words (outside and inside of chunks). You +can open the knitted output for each vignette in your Internet browser +as a guide while you write code in your own RMarkdown file. Or you can +open the vignette RMarkdown file and your own RMarkdown file side by +side in RStudio by adding a third column in the pane layout:

+
    +
  • Tools
  • +
  • Global Options
  • +
  • Pane Layout
  • +
  • Select the Add Column option to add another Source pane in a third +column
  • +
  • Open the original vignette RMarkdown file in one source pane, and +your own RMarkdown file in the other source pane
  • +
+

If you’re already familiar with writing R code and using RMarkdown +files, then you can open the original RMarkdown file for each vignette +and run the code inside each file as you work through each vignette +(with some modification of paths to reflect folders and files on your +local computer). If you want to preserve the original code in each +RMarkdown file, you can create a copy of each vignette and modify the +copies instead.

+

+Clean your global environment +

+

Your global environment is your virtual workspace in R, and can hold +different packages and objects to facilitate your data analysis and +programming objectives. You can see the different packages and objects +currently loaded in your environment by clicking on the tab +“Environment” in the pane that also includes the “History” and +“Connections” tab.

+

If you started a new RStudio session then your global environment +will likely be empty. But if you’re working in an older session or maybe +in an RProject workspace, then your global environment will likely have +packages and objects already loaded. Cleaning your global environment +can be good practice to ensure that you’re starting a new coding session +with a fresh workspace. If you don’t clean your global environment, even +when using the same code between sessions, then you risk using old +versions of objects that don’t reflect your newest changes to the +code.

+

You can clean your global environment directly from the RStudio +interface by clicking on the broom icon below the “Environment” tab (hit +“Yes” with “hidden objects” selected).

+

You can also clean your global environment by running the code below. +You can run the code inside this chunk in different ways:

+
    +
  • Click on the small green arrow icon on the top right side of the +chunk to run the code in this chunk only

  • +
  • Place your cursor anywhere within the line of code, then go up to +the “Run” icon on the top right of the Source pane that has a white +square and green arrow. In the dropdown menu that appears, select “Run +Selected Lines” or “Run Current Chunk” (see the keyboard shortcuts next +to each)

  • +
  • Place your cursor anywhere within the line of code and use the +keyboard shortcut Ctrl + Enter to run the current line of code

  • +
  • Place your cursor anywhere within the line of code and use the +keyboard shortcut Ctrl + Shift + Enter to run all code in the current +chunk

  • +
+

The first keyboard shortcut to run one line of code at a time can be +helpful to see the output of each line of code at a time and check for +errors.

+
rm(list = ls())
+

The code above to clean your global environment is a nested +expression with 2 functions: rm() and ls(). +The () notation is used for functions in R. Functions are +operations that you can apply in your own code by using the name of a +specific function. R has a set of base functions that you can access +without loading any additional packages, including the two functions +above (rm() and ls()).

+

+Access R function documentation +

+

You can access the documentation for these functions by clicking on +the “Help” tab in the pane that also has “Files” and “Plots”, and typing +the name of the function in the search bar. You can also access function +documentation by running the following code:

+
?rm
+
?ls
+

For each function, the documentation holds specific sections that can +be useful for understanding how the function works, especially the +function’s arguments. A function’s arguments are the values that it +expects the user to provide in order to guide or customize the +operation. Many functions will have preset default values for some +arguments that will be used when you don’t provide specific values for +arguments.

+

For instance, the function rm() has an argument called +list (followed by an =, which is used to pass +a specific value to the argument). In order for rm() to +clean your global environment in the way that you want, you need to +provide information after the argument list. To remove +everything in your global environment, we’re using the output of the +function ls(), which is a function that lists the names of +all of the objects in your global environment.

+

+Install and load packages +

+

After cleaning the global environment, you still need to set up your +virtual workspace for the upcoming coding session. One important step +here is to make sure you have access to functions that you need but +which aren’t accessible through the base R functions. For instance, the tidyverse is a +set of R packages that provide useful functions and expressions for data +science purposes.

+

If you don’t already have tidyverse installed on your local computer, +then you need to install the tidyverse in order to access these +functions. The code below installs the tidyverse from CRAN, the Comprehensive R Archive +Network that holds thousands of R packages online.

+
# Install the tidyverse from CRAN
+install.packages("tidyverse")
+

In the chunk above, I added a comment using the # +symbol. Any text that you write after a # symbol will be +treated as text and will not be run as code. It’s good practice to +comment your code, especially as you’re learning more about how to code +in R. For biologists, continuing to comment your code even when you’re a +coding expert can also be good practice. Commenting and documenting your +work makes the code that you publish with manuscripts or software tools +more accessible to others in the community.

+

Once you’ve installed the tidyverse on your computer, you do not need +to install it again. But you do need to use the function +library() to load the package into your global environment +in order to access functions that are held inside of the tidyverse +packages:

+
library(tidyverse)
+

+Coding shortcuts +

+

RStudio has a number of useful keyboard shortcuts for writing code. +You can find these shortcuts by going to Tools at the header of the +RStudio window, then Keyboard Shortcuts Help, which will pull up a popup +window with all of the default keyboard shortcuts. You already learned +some keyboard shortcuts above about how to run code inside of an +RMarkdown chunk. Some additional useful keyboard shortcuts are “Shift + +Ctrl + C”, which can be used to comment one or multiple lines of code or +create new comments, and “Ctrl + Alt + I”, which automatically creates a +new RMarkdown chunk.

+

Another useful type of shortcut provided by RStudio is tab completion +of code. For instance, in the chunk below, after writing +libr and pressing Tab, you should see a small popup window +appear that lists all of the available functions, packages, or objects +that start with the pattern “libr”. You can use the arrow keys to select +the option that you’d like and hit Enter to complete the line (for +instance, to write library() for loading a package).

+
libr
+

+Troubleshooting incomplete statements +

+

As you write and run code in RStudio, it’s important to keep an eye +on the console. When you see the symbol > in the +console, this means that the console finished running code and is ready +for another operation. However, when you see the symbol + +in the console, this means that the statement you just ran was +incomplete. Incomplete lines of code can occur when you have a typo so +that a statement isn’t fully closed, like when you miss an opening or +closing parenthesis in a function. Below is an example of an incomplete +statement:

+
library(tidyverse
+

When you run the code above, you should see a + symbol +appear in the console. This is because you’re missing a parenthesis to +close the function library(). There are two ways to solve +this problem. First, if you know what is missing from the statement, you +can type the missing bit into the console and hit “Enter”. Or, you can +click in the console, and then press “Esc”, which will remove the +incomplete statement and restart the console for additional code. It’s +good practice to keep an eye on the console to check that the code +you’re running is producing output. If you run a lot of code and don’t +see the output that you expect, it may be because you included an +incomplete statement, and it would be better to clean the console and +check your code before running it again.

+

+Create your working directory +

+

The next important step for setting up your virtual workspace is to +decide on the working directory that you will use for the coding +session. A directory is a location (a folder) on your computer where R +will search for files to read in. When you write out files from R, those +files can also be written to your working directory.

+

You can use the function getwd() to check your current +working directory.

+
getwd()
+
## [1] "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes"
+

My working directory is set by default to the folder on my computer +where I saved this RMarkdown file. To work in a separate directory that +holds only data generated from these vignettes, you can create a new +directory or folder on your computer:

+
?dir.create
+
+dir.create("/home/gsvidaurre/Desktop/ABISSMAL_vignettes")
+

In the code above, you’ll need to replace the path (or the list of +folder names) to reflect where you want to save the new folder called +ABISSMAL_vignettes (or another name of your choice) on your +own computer. For those using Windows, if you copy and paste a path into +R directly from your computer then you may need to change the direction +of the slashes.

+

In introductory R coding courses, it’s common to formally set your +working directory before a coding session by using the function +setwd(). It’s good practice to avoid using +setwd() in code that that you want to share with +collaborators or share more widely in the spirit of open science +(e.g. accompanying a publication or a new tool). When you use +setwd() and share the code with others, you’re assuming +that someone else has the same working directory on their computer, +which generally will not be true. There are other ways in which you can +specify your working directory throughout your code without using +setwd().

+

For instance, let’s say that we want to make a copy of the first +vignette and then save that copy inside of the directory that we created +above. We can use a base R functions to copy and save the file to the +correct working directory. You will need to update the paths below to +reflect the folder where the vignettes are saved on your computer, as +well as your own working directory:

+
file.copy(
+  from = "/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/Vignette_01_Introduction.Rmd",
+  to = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd"
+    )
+

In the code above, we’re specifying 2 arguments to the function +file.copy(). The first argument specifies the location of +the file that we want to copy +(/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/vignettes/) and the +name of the file (Vignette_01_Introduction.Rmd). The second argument +specifies the location where we want to save this copy +(/home/gsvidaurre/Desktop/ABISSMAL_vignettes/), and a new file name for +the copied file (Vignette_01_Introduction_copy.Rmd). In each argument, +the file paths are surrounded by 2 double quotes, in order to indicate +that the text inside each pair of quotes should be considered character +information or a “character string” (a formal term for text included in +R code).

+

When you run the line of code above, the output in the console should +read “[1] TRUE” if the file was successfully copied and saved. You can +check that the function worked correctly by opening a file window on +your computer in this new directory. You can also use the base R +function list.files() to check whether the copied file +exists in the working directory. The output of list.files() +is a list of all of the files contained within the new directory, and +that list should contain a single file: +“Vignette_01_Introduction_copy.Rmd”.

+
list.files(path = "/home/gsvidaurre/Desktop/ABISSMAL_vignettes")
+

The code above is an example of how you can specify your working +directory in the code that you write without needing to use +setwd(). In the following vignettes, you will see other +examples of how to specify your working directory without +setwd(). Before moving on to the next vignette, you can +remove the file that you copied to your working directory:

+
file.remove("/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Vignette_01_Introduction_copy.Rmd")
+

In the next vignette, you will start creating and manipulating +objects in R that can be used in the ABISSMAL data processing and +analysis pipeline.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_03_SimulateData.Rmd b/R/vignettes/Vignette_03_SimulateData.Rmd new file mode 100644 index 0000000..4cfe4da --- /dev/null +++ b/R/vignettes/Vignette_03_SimulateData.Rmd @@ -0,0 +1,361 @@ +--- +title: "Vignette 03: Simulate Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette overview and learning objectives

+ +In this third vignette, we will create simulated datasets of animal movements detected by different sensors. Simulating these datasets replaces the data collection process pipeline used by ABISSMAL to collect data from live animals. Generating simulated datasets will provide you more opportunities to practice basic coding skills, and will also allow you to have more control over the process of data collection in order to understand the subsequent data analysis steps. If you want to see movement detection datasets collected from live birds with ABISSMAL, and the code used to analyze those datasets, then check out the [methods manuscript preprint](https://ecoevorxiv.org/repository/view/6268/) that has links to publicly available data and code. + +Throughout the process of creating simulated datasets in this vignette, you will continue to use coding skills that you learned in vignette 02, and you will learn additional skills that include: + +1. Creating objects like vectors and data frames +2. Data types in R +3. Indexing and manipulating objects +4. Using conditional statements +5. Piping expressions through the tidyverse + +

Load packages

+ +In the previous vignette, you installed a set of data science packages called the `tidyverse`. You also learned about working directories and created a directory on your computer to save the data or image files that you generate across vignettes. + +Whenever you start a new RMarkdown file or R script, it's important to set up your virtual workspace before starting data analysis. In the chunk below, you will clean your global environment and load the tidyverse. This is code that you already saw in vignette 02, but now these lines of code are together in one chunk. +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Clean global environment + +library(tidyverse) # Load the set of tidyverse packages + +``` + +

Create a path object

+ +The next step is to specify your working directory. In vignette 02, you used a string (text contained in double quotes to indicate a character string used inside of R code) to specify your working directory while using different functions. Using the same long character string over and over is a lot of copy-pasting. Instead, you can save the character string for your working directory as a new object in R. + +You can create a `path` object by writing out the name of the object on the left (without quotes), then the object creation symbols `<-`, and then the information that you want to store in this object (your working directory as a character string in quotes). +```{r eval = TRUE} + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" + +``` + +In the code above, you created an object called `path`. You can see contents of the object `path` by writing out the object name and running that code in the console: +```{r eval = TRUE} + +path + +``` + +You can also see the contents of `path` by clicking on the "Environment" tab and checking out the column to the right of the object name. `path` is a new object that holds information about your working directory, and it's available in your global environment for future operations. + +You can practice removing the `path` object from your global environment by writing and running the code `rm(list = "path")`. After doing this, make sure to recreate `path` using the code above. + +

Create timestamps of simulated detections of animal movements

+ +The primary data collected by the ABISSMAL tracking system are timestamps indicating the moment of time when a sensor activated and recorded movement. Many (but not all) of these detections can be attributed to one or more animals moving near a sensor, such as a bird moving through a radio frequency identification (RFID) antenna mounted in the entrance of a nest container. In the subsequent code chunks, we will generate simulated data that represent RFID and infrared beam breaker detection datasets. + +In this simulated example, let's assume that we're collecting data for 2 adult birds at a nest container. The RFID antenna is mounted in the entrance of nest container. One pair of infrared beam breakers, the "outer" pair, is mounted in front of the RFID antenna to catch movements just outside of the container entrance. A second pair of infrared beam breakers, the "inner" pair, is mounted behind the RFID antenna to catch movements just inside of the container entrance. In this simulated setup, the outer beam breakers will activate when a bird enters the nest container, followed by the RFID antenna and the inner beam breakers. When a bird leaves the nest container, the inner beam breakers should activate first, followed by the RFID antenna and then the outer beam breakers. + +We will start by creating an object that holds simulated timestamps for the RFID antenna. This object will be called `rfid_ts`, and it will hold 4 timestamps in hours:minutes:seconds format. Each timestamp will be surrounded by double quotes to denote that we are using text or character string information here. + +These timestamps are combined inside of the same object by using the function `c()`. This function `c()` concatenates values separated by commas into a vector or list object. Above you created an object called `path` without using `c()`, but that object had a single value. Using `c()` allows you to combine multiple values into a vector that will have more than 1 element. +```{r} + +# Create a vector of 4 RFID timestamps or 4 elements in HH:MM:SS format +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") + +``` + +You can check out the different properties of the `rfid_ts` object after it's made: +```{r} + +rfid_ts # Run the object name to see its contents + +is.vector(rfid_ts) # A binary value indicating that rfid_ts is a vector + +class(rfid_ts) # A vector with data of type "character" + +length(rfid_ts) # This vector has 4 elements + +``` + +Let's continue by simulating 2 entrance and 2 exit movement events. We can choose timestamps for the outer pair of infrared beam breakers that precede the RFID timestamps and timestamps for the inner beam breaker pai that follow the RFID antenna to simulate an entrance event. We can simulate detections by these sensors in the opposite order (inner beam breakers, then RFID, followed by the outer beam breakers) to simulate an exit event. We will offset detections from each sensor within the entrance and exit movement events by 1 second. +```{r} + +# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit +o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") + +``` + +Birds will sometimes perch in the nest container entrance, and the sensors should pick up this behavior. You can add some perching events to the simulated dataset. Here we will simulate perching events for the RFID data only. +```{r} + +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") + +``` + +In the code above, we modified the object `rfid_ts` by using the function `c()` to add 10 more timestamps to this vector, for a total of 14 elements. Check out the structure of the modified `rfid_ts` object using the function `glimpse()`: +```{r} + +# See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements +glimpse(rfid_ts) + +``` + +Another important type of information to simulate is noise in the sensor detections. For instance, the RFID antenna can fail to detect an individual's passive integrated transponder (PIT) tag, and the infrared beam breakers can activate when birds leave nesting material hanging in the entrance of the container. In both of these cases, the infrared beam breakers should activate when the RFID antenna does not. +```{r} + +# Simulate some RFID detection failures across both beam breaker pairs +# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") + +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) + +# Simulate some stray detections for the outer beam breaker +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") + +glimpse(o_irbb_ts) + +``` + +You've created simulated, noisy datasets of detections of animal movements, but currently, these datasets are encoded across separate vector objects and do not have important metadata. For instance, some useful metadata includes information about the experimental replicate, the date, and for the RFID data, the unique alphanumeric code of each PIT tag detected by the RFID antenna. Some of this metadata is critically important for downstream analyses (e.g. the date and PIT tag codes). + +Vectors are useful data structures in R, but one limitation of vectors is that you cannot combine different data types into the same object. You can try this out with the code below: +```{r} + +# Create a vector with character, numeric, and binary data types +# Since you're not saving the output into an object, the result will print directly to the console +# You should see that all elements are forced to be character strings in double quotes +c("1", 1, TRUE, FALSE) + +# Create a vector with numeric and binary data types +# You should see that all elements have been forced to numeric. The TRUE and FALSE binary values are converted to the underlying numeric values used to store binary information (TRUE is stored as 1, and FALSE is stored as 0) +c(1, 1, TRUE, FALSE) + +``` + +When you try to combine character, numeric, and binary data types in the same vector, all vector elements will be forced to the character data type. A similar thing happens when you try to combine numeric and binary data types, but the vector is forced to numeric. Note that the binary values TRUE and FALSE are equivalent to the numeric values 1 and 0, respectively. + +

Create metadata vectors

+ +For downstream analyses, you need to be able to create metadata of different data types, and then combine the primary data for each sensor (timestamps) with this metadata. Start by creating vectors of metadata for the RFID timestamps, including information about the experimental replicate, the date, and the PIT tag identity for each detection. + +Make a vector of metadata about the experimental replicate. Here, instead of writing out the same information many times, you'll use the function `rep()` to repeat the information about the experimental replicate several times. In order to set the number of times that the experimental replicate information is repeated, you'll also use the function `length()` to get the length of the `rfid_ts` vector, and then feed that result to `rep()`. Creating a metadata vector that is the same length as the `rfid_ts` vector will be useful for combining these vectors into a single object later. +```{r} + +# The documentation tells us that rep() expects two arguments, `x` and `times` +?rep + +# Create a vector with information about the experimental replicate +# The argument `x` contains the metadata information that will be repeated +# The argument `times` specifies the number of times that the information will be repeated +exp_rep <- rep(x = "Pair_01", times = length(rfid_ts)) + +glimpse(exp_rep) + +# You can also run code without writing out the argument names, as long as the arguments are written in the same order that the function expects: +exp_rep <- rep("Pair_01", length(rfid_ts)) + +glimpse(exp_rep) + +``` + +Using `times = length(rfid_ts)` is better practice than manually entering the length of `rfid_ts` (e.g. `times = 14`). When you manually enter a value for `times`, you're assuming that the `rfid_ts` has not changed within and between coding sessions, which can be a dangerous assumption. By using `times = length(rfid_ts)`, you are ensuring that the code above will create a vector of metadata the same length as `rfid_ts` regardless of whether you made additional modifications to `rfid_ts` above. + +You can use a conditional statement to confirm that the vector of metadata is the same length as the vector of RFID timestamps. Conditional statements can be useful ways to check assumptions in your code, or to efficiently build new datasets and functions. +```{r} + +# If this condition is met, then the result in the console should be "[1] TRUE" +length(rfid_ts) == length(exp_rep) + +``` + +In the conditional statement above, you're using the symbols `==` to ask if the two vectors `rfid_ts` and `exp_rep` are the same length. + +You can also use the symbols `!=` to ask if the two vectors are *not* the same length: +```{r} + +# This statement should yield FALSE, because these vectors are the same length +length(rfid_ts) != length(exp_rep) + +``` + +As an example, you can modify `rfid_ts` so that it is a different length than `exp_rep`. Below you'll see some different ways that you can filter out 4 elements of the vector `rfid_ts` so that this vector is 10 elements long. +```{r} + +## Create numeric indices for filtering an object + +# You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts +5:length(rfid_ts) + +# You can also use the function `seq()` to create the same sequence of numeric indices +seq(from = 5, to = length(rfid_ts), by = 1) + +# If you want to filter out non-consecutive elements, you can create a vector of indices with the function `c()` +c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14) + +## Filter an object using numeric indices + +# When you put any of the expressions above inside of square brackets after the object name you will pull out elements 5 to the length of rfid_ts and drop the first 4 elements +rfid_ts[5:length(rfid_ts)] + +rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)] + +rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)] + +# Finally, you can use any of the methods above to create a sequence of indices that you want to drop, and then use the `-` symbol inside of the square brackets to remove those indices. For instance: +rfid_ts[-c(1:4)] # the numbers must be wrapped in `c()` in order for this inverted filtering to work + +``` + +Next, you can check whether the modified version of `rfid_ts` is the same length as `exp_rep`: +```{r} + +# This statement should yield TRUE, because these vectors are not the same length +length(rfid_ts[-c(1:4)]) != length(exp_rep) + +``` + +

Create data frames with primary data and metadata

+ +For later analyses, it is really important to have the metadata accompany your primary data in the very same object. To combine the primary data and metadata for these simulated datasets, you can rely on an object called a "data frame". Data frames are similar to spreadsheets. They have 2 dimensions (rows and columns), and you can store multiple different types of data in the same data frame. You can also write out data frames to spreadsheets that exist as physical files on your computer. + +When you try making a data frame with vectors of different length, you should get an error message stating that the two arguments provided have different numbers of rows: +```{r eval = FALSE} + +sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)]) + +# "Error in data.frame(exp_rep, rfid_ts[-c(1:4)]) : + # arguments imply differing number of rows: 14, 10" + +``` + +Now use the full `exp_rep` and `rfid_ts` vectors to create the data frame, in which these vectors become columns of the data frame: +```{r} + +sim_dats <- data.frame(exp_rep, rfid_ts) + +glimpse(sim_dats) + +``` + +When you check out the structure of the new data frame `sim_dats`, you can see that it has 14 rows and 2 columns. For each column (after the "$" symbol), you can see the column name (here `exp_rep` and `rfid_ts`), the type of data in each column (both columns are type "character"), and then a preview of the first values in each column. + +You can change the column names by adding a new name and `=` before each vector. Here the vector `exp_rep` becomes the column `replicate` and the vector `rfid_ts` becomes the column `timestamps`` +```{r} + +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +glimpse(sim_dats) + +``` + +We can add additional metadata to this data frame now that it's been created. For instance, it would be useful to have information about the date when these timestamps were collected. You can start by adding a column for the year, which will be data type "dbl" (which stands for "double" or "numeric"): +```{r} + +sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts))) + +glimpse(sim_dats) + +``` + +The year column has a strange name when we add the new column using `cbind()`. The column name is the expression that you wrote inside of `cbind()`. You can use the function `names()` and square bracket indexing to change the strange column name to "year": +```{r} + +# This function returns a vector of the column names of the data frame +names(sim_dats) + +# Use square bracket indexing and the function `ncol()` to find the last column name +ncol(sim_dats) # There are 3 columns in this data frame + +# This expression gets you the name of the last column +names(sim_dats)[ncol(sim_dats)] + +# Then you can overwrite the last column name with a new name +names(sim_dats)[ncol(sim_dats)] <- "year" + +# Confirm that the name was changed correctly +names(sim_dats) +glimpse(sim_dats) + +``` + +

Create data frames using the tidyverse

+ +In the chunk above, you used base R code to add a new column and to update the column name. It took more than a few lines of code to carry out these operations. You could reduce the amount of code needed for these steps by removing the lines included to check your work. But another way that you can reduce the amount of code you're writing for these operations is to use tidyverse notation and functions: +```{r} + +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +# Use the tidyverse to add the year as the 3rd column +sim_dats <- sim_dats %>% + dplyr::mutate( + year = rep(2023, length(rfid_ts)) + ) + +glimpse(sim_dats) + +``` + +You added a column for the year with the correct column name in fewer lines of code. In this tidyverse syntax, the symbol `%>%` represents a piping operation, in which you're using one object as the input for a subsequent operation. Here, you're using the object `sim_dats` at the input for the function `mutate()`, which you used to create the column `year`. + +The notation `dplyr::` before `mutate()` indicates that the function `mutate()` should be sourced from the package called `dplyr`. Including the package name with 2 colons before a function name is important when there are multiple functions loaded in your global environment that have the same name. For instance, if you use other packages that also have functions called `mutate()`, and you don't specify which package you want to use, then you could end up with immediate errors (the code fails to run) or worse, you could run the wrong `mutate()` operation for a given analysis (which can lead to errors down the line that are harder to trace). + +Piping operations can simplify the code that you write, so that you don't have to create as many intermediate objects. On the other hand, for the same reason it can take practice to troubleshoot piping operations, especially when they become very long. A useful way to check intermediate results within long piping operations is to include the function `glimpse()` between different piping steps: +```{r} + +# Make the data frame again with only 2 columns +sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts) + +# Use the tidyverse to add the year as the 3rd column +sim_dats %>% + glimpse() %>% # See the structure of the first version sim_dats + dplyr::mutate( + # The nrow(.) expression means "get the number of rows for the current object", which in this case is sim_dats + year = rep(2023, nrow(.)) + ) %>% + glimpse() # See the structure of the latest sim_dats with the new column "year" + +``` + +In the code above, you also learned a new way to repeat a value within a piping operation by using the notation '.' inside of a function, which means that you will perform the given operation (getting the number of rows) on the current object (`sim_dats`) piped into the expression (the `nrow()` function). + +You can use a similar rule of thumb to add two more columns for the month and day to the data frame: +```{r} + +# Use the tidyverse to add the year as the 3rd column +sim_dats %>% + glimpse() %>% # See the structure of the original sim_dats + dplyr::mutate( + year = 2023 + ) %>% + glimpse() %>% # See the structure of the intermediate sim_dats with the new column year + # Also add columns for the month and day + dplyr::mutate( + month = 08, + day = 01 + ) %>% + glimpse() # See the structure of the final sim_dats with the additional new columns month and day + +``` + +In the code above, you added 2 more numeric columns to the data frame and you did this without needing to use the `rep()` function. Since you piped an existing data frame into each `dplyr::mutate()` expression, the single value that you specified for each of the year, month, and day columns was automatically repeated to fill the total rows in the data frame. Specifying a single value for a new column can help reduce the amount of code that you write, but only when you truly want the same value repeated across all rows of a data frame. + +You did not save these modifications that you made to `sim_dats` in an object, so the output was printed to the console only. In the next vignette, you will write out this data frame of simulated RFID and IRBB data to spreadsheets as physical files on your computer. \ No newline at end of file diff --git a/R/vignettes/Vignette_03_SimulateData.html b/R/vignettes/Vignette_03_SimulateData.html new file mode 100644 index 0000000..21f1553 --- /dev/null +++ b/R/vignettes/Vignette_03_SimulateData.html @@ -0,0 +1,2102 @@ + + + + + + + + + + + + + + + +Vignette 03: Simulate Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this third vignette, we will create simulated datasets of animal +movements detected by different sensors. Simulating these datasets +replaces the data collection process pipeline used by ABISSMAL to +collect data from live animals. Generating simulated datasets will +provide you more opportunities to practice basic coding skills, and will +also allow you to have more control over the process of data collection +in order to understand the subsequent data analysis steps. If you want +to see movement detection datasets collected from live birds with +ABISSMAL, and the code used to analyze those datasets, then check out +the methods +manuscript preprint that has links to publicly available data and +code.

+

Throughout the process of creating simulated datasets in this +vignette, you will continue to use coding skills that you learned in +vignette 02, and you will learn additional skills that include:

+
    +
  1. Creating objects like vectors and data frames
  2. +
  3. Data types in R
  4. +
  5. Indexing and manipulating objects
  6. +
  7. Using conditional statements
  8. +
  9. Piping expressions through the tidyverse
  10. +
+

+Load packages +

+

In the previous vignette, you installed a set of data science +packages called the tidyverse. You also learned about +working directories and created a directory on your computer to save the +data or image files that you generate across vignettes.

+

Whenever you start a new RMarkdown file or R script, it’s important +to set up your virtual workspace before starting data analysis. In the +chunk below, you will clean your global environment and load the +tidyverse. This is code that you already saw in vignette 02, but now +these lines of code are together in one chunk.

+
rm(list = ls()) # Clean global environment
+
+library(tidyverse) # Load the set of tidyverse packages
+

+Create a path object +

+

The next step is to specify your working directory. In vignette 02, +you used a string (text contained in double quotes to indicate a +character string used inside of R code) to specify your working +directory while using different functions. Using the same long character +string over and over is a lot of copy-pasting. Instead, you can save the +character string for your working directory as a new object in R.

+

You can create a path object by writing out the name of +the object on the left (without quotes), then the object creation +symbols <-, and then the information that you want to +store in this object (your working directory as a character string in +quotes).

+
path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"
+

In the code above, you created an object called path. +You can see contents of the object path by writing out the +object name and running that code in the console:

+
path
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"
+

You can also see the contents of path by clicking on the +“Environment” tab and checking out the column to the right of the object +name. path is a new object that holds information about +your working directory, and it’s available in your global environment +for future operations.

+

You can practice removing the path object from your +global environment by writing and running the code +rm(list = "path"). After doing this, make sure to recreate +path using the code above.

+

+Create timestamps of simulated detections of animal movements +

+

The primary data collected by the ABISSMAL tracking system are +timestamps indicating the moment of time when a sensor activated and +recorded movement. Many (but not all) of these detections can be +attributed to one or more animals moving near a sensor, such as a bird +moving through a radio frequency identification (RFID) antenna mounted +in the entrance of a nest container. In the subsequent code chunks, we +will generate simulated data that represent RFID and infrared beam +breaker detection datasets.

+

In this simulated example, let’s assume that we’re collecting data +for 2 adult birds at a nest container. The RFID antenna is mounted in +the entrance of nest container. One pair of infrared beam breakers, the +“outer” pair, is mounted in front of the RFID antenna to catch movements +just outside of the container entrance. A second pair of infrared beam +breakers, the “inner” pair, is mounted behind the RFID antenna to catch +movements just inside of the container entrance. In this simulated +setup, the outer beam breakers will activate when a bird enters the nest +container, followed by the RFID antenna and the inner beam breakers. +When a bird leaves the nest container, the inner beam breakers should +activate first, followed by the RFID antenna and then the outer beam +breakers.

+

We will start by creating an object that holds simulated timestamps +for the RFID antenna. This object will be called rfid_ts, +and it will hold 4 timestamps in hours:minutes:seconds format. Each +timestamp will be surrounded by double quotes to denote that we are +using text or character string information here.

+

These timestamps are combined inside of the same object by using the +function c(). This function c() concatenates +values separated by commas into a vector or list object. Above you +created an object called path without using +c(), but that object had a single value. Using +c() allows you to combine multiple values into a vector +that will have more than 1 element.

+
# Create a vector of 4 RFID timestamps or 4 elements in HH:MM:SS format
+rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00")
+

You can check out the different properties of the +rfid_ts object after it’s made:

+
rfid_ts # Run the object name to see its contents
+
## [1] "10:00:00" "10:05:00" "11:00:00" "11:05:00"
+
is.vector(rfid_ts) # A binary value indicating that rfid_ts is a vector
+
## [1] TRUE
+
class(rfid_ts) # A vector with data of type "character"
+
## [1] "character"
+
length(rfid_ts) # This vector has 4 elements
+
## [1] 4
+

Let’s continue by simulating 2 entrance and 2 exit movement events. +We can choose timestamps for the outer pair of infrared beam breakers +that precede the RFID timestamps and timestamps for the inner beam +breaker pai that follow the RFID antenna to simulate an entrance event. +We can simulate detections by these sensors in the opposite order (inner +beam breakers, then RFID, followed by the outer beam breakers) to +simulate an exit event. We will offset detections from each sensor +within the entrance and exit movement events by 1 second.

+
# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit
+o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
+i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59")
+

Birds will sometimes perch in the nest container entrance, and the +sensors should pick up this behavior. You can add some perching events +to the simulated dataset. Here we will simulate perching events for the +RFID data only.

+
rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05")
+

In the code above, we modified the object rfid_ts by +using the function c() to add 10 more timestamps to this +vector, for a total of 14 elements. Check out the structure of the +modified rfid_ts object using the function +glimpse():

+
# See the object structure in a format that includes the data type ("chr"), the number of elements ([1:14]), and a preview of the first few elements
+glimpse(rfid_ts)
+
##  chr [1:14] "10:00:00" "10:05:00" "11:00:00" "11:05:00" "08:00:00" ...
+

Another important type of information to simulate is noise in the +sensor detections. For instance, the RFID antenna can fail to detect an +individual’s passive integrated transponder (PIT) tag, and the infrared +beam breakers can activate when birds leave nesting material hanging in +the entrance of the container. In both of these cases, the infrared beam +breakers should activate when the RFID antenna does not.

+
# Simulate some RFID detection failures across both beam breaker pairs
+# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits
+o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
+i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24")
+
+glimpse(o_irbb_ts)
+
##  chr [1:8] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+
glimpse(i_irbb_ts)
+
##  chr [1:8] "10:00:01" "10:04:59" "11:00:01" "11:04:59" "06:05:04" ...
+
# Simulate some stray detections for the outer beam breaker
+o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11")
+
+glimpse(o_irbb_ts)
+
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+

You’ve created simulated, noisy datasets of detections of animal +movements, but currently, these datasets are encoded across separate +vector objects and do not have important metadata. For instance, some +useful metadata includes information about the experimental replicate, +the date, and for the RFID data, the unique alphanumeric code of each +PIT tag detected by the RFID antenna. Some of this metadata is +critically important for downstream analyses (e.g. the date and PIT tag +codes).

+

Vectors are useful data structures in R, but one limitation of +vectors is that you cannot combine different data types into the same +object. You can try this out with the code below:

+
# Create a vector with character, numeric, and binary data types
+# Since you're not saving the output into an object, the result will print directly to the console
+# You should see that all elements are forced to be character strings in double quotes
+c("1", 1, TRUE, FALSE)
+
## [1] "1"     "1"     "TRUE"  "FALSE"
+
# Create a vector with numeric and binary data types
+# You should see that all elements have been forced to numeric. The TRUE and FALSE binary values are converted to the underlying numeric values used to store binary information (TRUE is stored as 1, and FALSE is stored as 0)
+c(1, 1, TRUE, FALSE)
+
## [1] 1 1 1 0
+

When you try to combine character, numeric, and binary data types in +the same vector, all vector elements will be forced to the character +data type. A similar thing happens when you try to combine numeric and +binary data types, but the vector is forced to numeric. Note that the +binary values TRUE and FALSE are equivalent to the numeric values 1 and +0, respectively.

+

+Create metadata vectors +

+

For downstream analyses, you need to be able to create metadata of +different data types, and then combine the primary data for each sensor +(timestamps) with this metadata. Start by creating vectors of metadata +for the RFID timestamps, including information about the experimental +replicate, the date, and the PIT tag identity for each detection.

+

Make a vector of metadata about the experimental replicate. Here, +instead of writing out the same information many times, you’ll use the +function rep() to repeat the information about the +experimental replicate several times. In order to set the number of +times that the experimental replicate information is repeated, you’ll +also use the function length() to get the length of the +rfid_ts vector, and then feed that result to +rep(). Creating a metadata vector that is the same length +as the rfid_ts vector will be useful for combining these +vectors into a single object later.

+
# The documentation tells us that rep() expects two arguments, `x` and `times`
+?rep
+
+# Create a vector with information about the experimental replicate
+# The argument `x` contains the metadata information that will be repeated
+# The argument `times` specifies the number of times that the information will be repeated
+exp_rep <- rep(x = "Pair_01", times = length(rfid_ts))
+
+glimpse(exp_rep)
+
##  chr [1:14] "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
+
# You can also run code without writing out the argument names, as long as the arguments are written in the same order that the function expects:
+exp_rep <- rep("Pair_01", length(rfid_ts))
+
+glimpse(exp_rep)
+
##  chr [1:14] "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" "Pair_01" ...
+

Using times = length(rfid_ts) is better practice than +manually entering the length of rfid_ts +(e.g. times = 14). When you manually enter a value for +times, you’re assuming that the rfid_ts has +not changed within and between coding sessions, which can be a dangerous +assumption. By using times = length(rfid_ts), you are +ensuring that the code above will create a vector of metadata the same +length as rfid_ts regardless of whether you made additional +modifications to rfid_ts above.

+

You can use a conditional statement to confirm that the vector of +metadata is the same length as the vector of RFID timestamps. +Conditional statements can be useful ways to check assumptions in your +code, or to efficiently build new datasets and functions.

+
# If this condition is met, then the result in the console should be "[1] TRUE"
+length(rfid_ts) == length(exp_rep)
+
## [1] TRUE
+

In the conditional statement above, you’re using the symbols +== to ask if the two vectors rfid_ts and +exp_rep are the same length.

+

You can also use the symbols != to ask if the two +vectors are not the same length:

+
# This statement should yield FALSE, because these vectors are the same length
+length(rfid_ts) != length(exp_rep)
+
## [1] FALSE
+

As an example, you can modify rfid_ts so that it is a +different length than exp_rep. Below you’ll see some +different ways that you can filter out 4 elements of the vector +rfid_ts so that this vector is 10 elements long.

+
## Create numeric indices for filtering an object
+
+# You can use : to create a sequence of numbers from indices 5 to the length of rfid_ts
+5:length(rfid_ts)
+
##  [1]  5  6  7  8  9 10 11 12 13 14
+
# You can also use the function `seq()` to create the same sequence of numeric indices
+seq(from = 5, to = length(rfid_ts), by = 1)
+
##  [1]  5  6  7  8  9 10 11 12 13 14
+
# If you want to filter out non-consecutive elements, you can create a vector of indices with the function `c()`
+c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)
+
##  [1]  1  3  5  6  8 10 11 12 13 14
+
## Filter an object using numeric indices
+
+# When you put any of the expressions above inside of square brackets after the object name you will pull out elements 5 to the length of rfid_ts and drop the first 4 elements
+rfid_ts[5:length(rfid_ts)]
+
##  [1] "08:00:00" "08:00:01" "08:00:02" "08:00:03" "11:30:00" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+
rfid_ts[seq(from = 5, to = length(rfid_ts), by = 1)]
+
##  [1] "08:00:00" "08:00:01" "08:00:02" "08:00:03" "11:30:00" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+
rfid_ts[c(1, 3, 5, 6, 8, 10, 11, 12, 13, 14)]
+
##  [1] "10:00:00" "11:00:00" "08:00:00" "08:00:01" "08:00:03" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+
# Finally, you can use any of the methods above to create a sequence of indices that you want to drop, and then use the `-` symbol inside of the square brackets to remove those indices. For instance:
+rfid_ts[-c(1:4)] # the numbers must be wrapped in `c()` in order for this inverted filtering to work 
+
##  [1] "08:00:00" "08:00:01" "08:00:02" "08:00:03" "11:30:00" "11:30:01"
+##  [7] "11:30:02" "11:30:03" "11:30:04" "11:30:05"
+

Next, you can check whether the modified version of +rfid_ts is the same length as exp_rep:

+
# This statement should yield TRUE, because these vectors are not the same length
+length(rfid_ts[-c(1:4)]) != length(exp_rep)
+
## [1] TRUE
+

+Create data frames with primary data and metadata +

+

For later analyses, it is really important to have the metadata +accompany your primary data in the very same object. To combine the +primary data and metadata for these simulated datasets, you can rely on +an object called a “data frame”. Data frames are similar to +spreadsheets. They have 2 dimensions (rows and columns), and you can +store multiple different types of data in the same data frame. You can +also write out data frames to spreadsheets that exist as physical files +on your computer.

+

When you try making a data frame with vectors of different length, +you should get an error message stating that the two arguments provided +have different numbers of rows:

+
sim_dats <- data.frame(exp_rep, rfid_ts[-c(1:4)])
+
+# "Error in data.frame(exp_rep, rfid_ts[-c(1:4)]) : 
+  # arguments imply differing number of rows: 14, 10"
+

Now use the full exp_rep and rfid_ts +vectors to create the data frame, in which these vectors become columns +of the data frame:

+
sim_dats <- data.frame(exp_rep, rfid_ts)
+
+glimpse(sim_dats)
+
## Rows: 14
+## Columns: 2
+## $ exp_rep <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_0…
+## $ rfid_ts <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00", "0…
+

When you check out the structure of the new data frame +sim_dats, you can see that it has 14 rows and 2 columns. +For each column (after the “$” symbol), you can see the column name +(here exp_rep and rfid_ts), the type of data +in each column (both columns are type “character”), and then a preview +of the first values in each column.

+

You can change the column names by adding a new name and += before each vector. Here the vector exp_rep +becomes the column replicate and the vector +rfid_ts becomes the column `timestamps``

+
sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts)
+
+glimpse(sim_dats)
+
## Rows: 14
+## Columns: 2
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+

We can add additional metadata to this data frame now that it’s been +created. For instance, it would be useful to have information about the +date when these timestamps were collected. You can start by adding a +column for the year, which will be data type “dbl” (which stands for +“double” or “numeric”):

+
sim_dats <- cbind(sim_dats, rep(2023, length(rfid_ts)))
+
+glimpse(sim_dats)  
+
## Rows: 14
+## Columns: 3
+## $ replicate                    <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01…
+## $ timestamps                   <chr> "10:00:00", "10:05:00", "11:00:00", "11:0…
+## $ `rep(2023, length(rfid_ts))` <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

The year column has a strange name when we add the new column using +cbind(). The column name is the expression that you wrote +inside of cbind(). You can use the function +names() and square bracket indexing to change the strange +column name to “year”:

+
# This function returns a vector of the column names of the data frame 
+names(sim_dats)
+
## [1] "replicate"                  "timestamps"                
+## [3] "rep(2023, length(rfid_ts))"
+
# Use square bracket indexing and the function `ncol()` to find the last column name
+ncol(sim_dats) # There are 3 columns in this data frame
+
## [1] 3
+
# This expression gets you the name of the last column
+names(sim_dats)[ncol(sim_dats)]
+
## [1] "rep(2023, length(rfid_ts))"
+
# Then you can overwrite the last column name with a new name
+names(sim_dats)[ncol(sim_dats)] <- "year"
+
+# Confirm that the name was changed correctly
+names(sim_dats)
+
## [1] "replicate"  "timestamps" "year"
+
glimpse(sim_dats)
+
## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

+Create data frames using the tidyverse +

+

In the chunk above, you used base R code to add a new column and to +update the column name. It took more than a few lines of code to carry +out these operations. You could reduce the amount of code needed for +these steps by removing the lines included to check your work. But +another way that you can reduce the amount of code you’re writing for +these operations is to use tidyverse notation and functions:

+
# Make the data frame again with only 2 columns
+sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts)
+
+# Use the tidyverse to add the year as the 3rd column
+sim_dats <- sim_dats %>% 
+  dplyr::mutate(
+    year = rep(2023, length(rfid_ts))
+  )
+
+glimpse(sim_dats)
+
## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

You added a column for the year with the correct column name in fewer +lines of code. In this tidyverse syntax, the symbol %>% +represents a piping operation, in which you’re using one object as the +input for a subsequent operation. Here, you’re using the object +sim_dats at the input for the function +mutate(), which you used to create the column +year.

+

The notation dplyr:: before mutate() +indicates that the function mutate() should be sourced from +the package called dplyr. Including the package name with 2 +colons before a function name is important when there are multiple +functions loaded in your global environment that have the same name. For +instance, if you use other packages that also have functions called +mutate(), and you don’t specify which package you want to +use, then you could end up with immediate errors (the code fails to run) +or worse, you could run the wrong mutate() operation for a +given analysis (which can lead to errors down the line that are harder +to trace).

+

Piping operations can simplify the code that you write, so that you +don’t have to create as many intermediate objects. On the other hand, +for the same reason it can take practice to troubleshoot piping +operations, especially when they become very long. A useful way to check +intermediate results within long piping operations is to include the +function glimpse() between different piping steps:

+
# Make the data frame again with only 2 columns
+sim_dats <- data.frame(replicate = exp_rep, timestamps = rfid_ts)
+
+# Use the tidyverse to add the year as the 3rd column
+sim_dats %>%
+  glimpse() %>% # See the structure of the first version sim_dats
+  dplyr::mutate(
+    # The nrow(.) expression means "get the number of rows for the current object", which in this case is sim_dats
+    year = rep(2023, nrow(.))
+  ) %>%
+  glimpse() # See the structure of the latest sim_dats with the new column "year"
+
## Rows: 14
+## Columns: 2
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+

In the code above, you also learned a new way to repeat a value +within a piping operation by using the notation ‘.’ inside of a +function, which means that you will perform the given operation (getting +the number of rows) on the current object (sim_dats) piped +into the expression (the nrow() function).

+

You can use a similar rule of thumb to add two more columns for the +month and day to the data frame:

+
# Use the tidyverse to add the year as the 3rd column
+sim_dats %>%
+  glimpse() %>% # See the structure of the original sim_dats
+  dplyr::mutate(
+    year = 2023
+  ) %>%
+  glimpse() %>% # See the structure of the intermediate sim_dats with the new column year
+  # Also add columns for the month and day
+  dplyr::mutate(
+    month = 08,
+    day = 01
+  ) %>% 
+  glimpse() # See the structure of the final sim_dats with the additional new columns month and day
+
## Rows: 14
+## Columns: 2
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## Rows: 14
+## Columns: 3
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## Rows: 14
+## Columns: 5
+## $ replicate  <chr> "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pair_01", "Pai…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+

In the code above, you added 2 more numeric columns to the data frame +and you did this without needing to use the rep() function. +Since you piped an existing data frame into each +dplyr::mutate() expression, the single value that you +specified for each of the year, month, and day columns was automatically +repeated to fill the total rows in the data frame. Specifying a single +value for a new column can help reduce the amount of code that you +write, but only when you truly want the same value repeated across all +rows of a data frame.

+

You did not save these modifications that you made to +sim_dats in an object, so the output was printed to the +console only. In the next vignette, you will write out this data frame +of simulated RFID and IRBB data to spreadsheets as physical files on +your computer.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_04_SaveData.Rmd b/R/vignettes/Vignette_04_SaveData.Rmd new file mode 100644 index 0000000..cbf4c6d --- /dev/null +++ b/R/vignettes/Vignette_04_SaveData.Rmd @@ -0,0 +1,700 @@ +--- +title: "Vignette 04: Save Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette overview and learning objectives

+ +In this fourth vignette, you will write out spreadsheets of simulated detections of animal movements to your computer. You will continue to use coding skills that you learned in the previous vignettes, and you'll learn additional skills that include: + +1. Indexing and filtering data frames +2. Checking data frame structure with base R and the tidyverse +3. Saving objects from R as physical files on your computer +4. Reading files from your computer into R +5. Writing and testing loops + +

Load packages and your working directory path

+ +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Clean global environment + +library(tidyverse) # Load the set of tidyverse packages + +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory + +``` + +

Create the simulated data

+ +In the code below, you'll recreate the simulated RFID and beam breaker datasets that you learned to build in the previous vignette. Here, the code from vignette 03 has been condensed into fewer chunks: +```{r} + +# Create a vector of 4 RFID timestamps in HH:MM:SS format +rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00") + +# Add perching events to the RFID data +rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05") + +glimpse(rfid_ts) + +``` + +```{r} + +# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit +o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01") +i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59") + +# Simulate some RFID detection failures across both beam breaker pairs +# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits +o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25") +i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24") + +# Simulate some stray detections for the outer beam breaker +o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11") + +glimpse(o_irbb_ts) +glimpse(i_irbb_ts) + +``` + +

Simulate 3 days of RFID data collection

+ +In the code below, you'll combine the vector of RFID timestamps that you made above with metadata in a data frame. This metadata will include the year, month, and day, as well as a column with 2 unique PIT tag identifiers (1 for each simulated individual), and a column with the sensor type. You should recognize some of this code from the previous vignette: +```{r} + +# Make a vector for the experimental replicate +exp_rep <- rep(x = "Nest_01", times = length(rfid_ts)) + +# Make a vector of the PIT tag codes +# Allocate the first 4 RFID detections to the first individual, the first perching event (4 detections) to the first individual, and the second perching event (6 detections) to the second individual +# The 3 rep() expressions are combined into a single vector using the c() function +PIT_tag <- c(rep("1357aabbcc", 4), rep("1357aabbcc", 4), rep("2468zzyyxx", 6)) + +# Make the data frame with the experimental replicate metadata and the timestamps +sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts) + +# Overwrite the data frame with the modified version that has columns for the year, month, and day +sim_dats_rfid <- sim_dats_rfid %>% + dplyr::mutate( + year = 2023 + ) %>% + dplyr::mutate( + month = 08, + day = 01 + ) %>% + # Add the PIT tag metadata as a new column + dplyr::mutate( + PIT_tag = PIT_tag + ) %>% + dplyr::mutate( + sensor_id = "RFID" + ) + +glimpse(sim_dats_rfid) + +``` + +Next, you can use this data frame to simulate data collection over more than one day. To create more observations (rows) for two additional days, you can append rows from a modified copy of `sim_dats_rfid` to itself. + +In the code below, you're piping `sim_dats_rfid` into `bind_rows()`, which indicates that this is the original object to which you want to append or add new rows. Then the code inside of `bind_rows()` indicates the data frame (the new rows) that will be appended to `sim_dats_rfid`. In this case, the code inside of `bind_rows()` pipes `sim_dats_rfid` into `dplyr::mutate()`, which is a function that you're using to modify the `day` column to reflect a subsequent day of data collection. Then you'll repeat this process to simulate a third day of data collection: +```{r} + +sim_dats_rfid <- sim_dats_rfid %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 02 + ) + ) %>% + bind_rows( + sim_dats_rfid %>% + dplyr::mutate( + day = 03 + ) + ) + +glimpse(sim_dats_rfid) # Triple the number of rows, looks good + +``` + +

Check data frame columns with base R

+ +You checked that simulated data frame has data collected over three days by looking at the structure of the new object. You can also check the unique values present in the column `day`. Below is a way of checking the unique values contained in a column of a data frame, using two different examples of base R notation for accessing columns: +```{r} + +# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console +sim_dats_rfid$day + +# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets +sim_dats_rfid[["day"]] + +# You can use the function unique() to see the unique values of any vector, including a column of a data frame +unique(sim_dats_rfid$day) # Three days, looks good + +unique(sim_dats_rfid[["day"]]) # Three days, looks good + +``` + +

Check data frame columns with the tidyverse

+ +You can also check the unique values in a column using functions from the tidyverse. In the expression below, you're piping `sim_dats_rfid` into the function `pull()`, which lets you pull the column `day` out of the data frame as a vector. Then that vector is piped into the function `unique()` to check the unique values contained within it. The function `unique()` does not need an argument inside of the parentheses here because you're already piping the output that it needs directly into the function. +```{r} + +# Three days, looks good +sim_dats_rfid %>% + pull(day) %>% + unique() + +``` + +

Simulate 3 days of beam breaker data collection

+ +Next, repeat this process of creating a data frame with metadata for the infrared beam breaker datasets. Since the beam breakers do not collect unique individual identity information, you will add columns for the year, month, day, and sensor type. You will also simulate data collection for the beam breakers over the same three days as the RFID system. +```{r} + +# Overwrite the vector exp_rep with a new vector the same length as o_irbb_ts and i_irbb_ts together +exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts)) + +# Here the timestamps of both beam breaker pairs have been added to the same column using c() +sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = c(o_irbb_ts, i_irbb_ts)) + +sim_dats_irbb <- sim_dats_irbb %>% + dplyr::mutate( + year = 2023, + month = 08, + day = 01, + # Add a unique sensor identifier by beam breaker pair + # Each unique label is repeated for the length of the vector of timestamps of each beam breaker pair + sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts))) + ) + +glimpse(sim_dats_irbb) + +sim_dats_irbb <- sim_dats_irbb %>% + bind_rows( + sim_dats_irbb %>% + dplyr::mutate( + day = 02 + ) + ) %>% + bind_rows( + sim_dats_irbb %>% + dplyr::mutate( + day = 03 + ) + ) + +glimpse(sim_dats_irbb) # Triple the number of rows, looks good + +# Three days, looks good +sim_dats_irbb %>% + pull(day) %>% + unique() + +``` + +

Save a data frame as a physical file

+ +The data frames that you create and manipulate in R can be saved as physical files in your working directory. You have many different file type options for saving data frames, but I recommend using .csv format since this file type is compatible with R, Microsoft Word, and other software. You can use the function `write.csv()` to save data frames to .csv spreadsheets on your computer: +```{r eval = FALSE} + +?write.csv + +``` + +In order to write a physical file to your working directory, you need to tell R 1) where to save the file and 2) the name of the file that you want to create. You can pass both of these pieces of information to `write.csv()` by combining your working directory path and the file name through the function `file.path()`. For this example, you'll create a test file as you practice using `write.csv()`: +```{r} + +# Create a custom file name by combining the path for your working directory with the file name that you want to write out +# The function file.path() will combine both pieces of information into a single file path +rfid_file <- file.path(path, "test_file.csv") + +# This object contains the location where the file will be saved, followed by the file name +rfid_file + +``` + +Next, you can pipe the data frame to `write.csv()` and specify additional information for creating the .csv file, such as whether you want to add a column of numeric row identifiers: +```{r eval = FALSE} + +sim_dats_rfid %>% + # Write out the data frame as a .csv spreadsheet. Do not include row names + #The "." below means that the function write.csv() will operate on the object that is piped in, which here is the data frame sim_dats_rfid + write.csv(x = ., file = rfid_file, row.names = FALSE) + +``` + +As specified in the function documentation for `write.csv()`, the function will include column names in the resulting spreadsheet by default. The function will also not append new information to the .csv if it already exists, so if you already created this file then it will be overwritten when you run the function again. + +Next, you can check that `write.csv()` worked as expected by using `list.files()` to check the files in your working directory. +```{r eval = FALSE} + +# List all files in the given path +list.files(path) + +``` + +You can also use `list.files()` to customize a search with the argument called `pattern`. Using the argument `pattern` here is similar to searching for a specific word inside of a text document. The dollar sign placed after ".csv" means that you're telling the function to search specifically for all files that *end* in the pattern ".csv": +```{r eval = FALSE} + +# List only files that end in the pattern ".csv" in the given path +list.files(path, pattern = ".csv$") + +``` + +

Read in a spreadsheet

+ +You can read one these files back into R with the function `read.csv()`. In the code below, you're piping the output of `read.csv()` directly into `glimpse()` to check out the structure of the resulting data frame. The output of the code is printed to the console, but is not saved inside of an object. +```{r eval = FALSE} + +read.csv(file.path(path, "test_file.csv")) %>% + glimpse() + +``` + +Now that you've practiced using `write.csv()` and `read.csv()`, you can delete the temporary file that you created by feeding the `rfid_file` object to the function `file.remove()`. +```{r eval = FALSE} + +rfid_file <- file.path(path, "test_file.csv") +rfid_file + +file.remove(rfid_file) + +``` + +

Save simulated data for the ABISSMAL workflow

+ +In the code below, you'll work through a pipeline to save the simulated data in the format and locations expected by the custom ABISSMAL functions. In order to use the ABISSMAL pipeline, the simulated raw data needs to be saved in a separate spreadsheet per sensor and day of data collection. These spreadsheets need to be saved inside of a folder per sensor as well. ABISSMAL will automatically save data in these ways when the tracking system is used to collect data from live animals. + +

Filter a data frame

+ +In the code below, you'll practice how to use the function `dplyr::filter()` to filter rows of a data frame by day and then save a filtered data frame with `write.csv()`. To filter a data frame, you can use a conditional statement inside of the function `dplyr::filter()`: +```{r} + +# Pipe the data frame into the filter() function +sim_dats_rfid %>% + # Filter the data frame by pulling out all rows in which the day column was equal to 1 + dplyr::filter(day == 1) %>% + glimpse() + +# Check that the filtering was done correctly by getting the unique values in the day column. Looks good +sim_dats_rfid %>% + dplyr::filter(day == 1) %>% + pull(day) %>% + unique() + +``` + +You can obtain similar results by inverting the conditional statement inside of `dplyr::filter()` to remove days that are **not** the second or third days of data collection. Below you added two conditional statements together using the symbol "&". +```{r} + +sim_dats_rfid %>% + # Filter the data frame by pulling out all rows in which the day column was not equal to 2 + dplyr::filter(day != 2 & day != 3) %>% + glimpse() + +# Check that the filtering was done correctly by getting the unique values in the day column. Looks good +sim_dats_rfid %>% + dplyr::filter(day != 2 & day != 3) %>% + pull(day) %>% + unique() + +``` + +Now that you've practiced filtering a data frame, you can combine this filtering step with writing out a .csv file that contains the filtered data frame with data collected on a single day: +```{r eval = FALSE} + +# Make a new directory inside of your working directory for saving data +file.path(path, "Data") # Check the new path +dir.create(file.path(path, "Data")) # Create the new path + +# Then make a new directory inside of the Data folder for the raw RFID data +file.path(path, "RFID") # Check the new path +dir.create(file.path(path, "Data", "RFID")) # Create the new path + +# Make the new file name with your working directory path +# Make sure to specify that the spreadsheet will be saved inside of the new folder "RFID" +rfid_file <- file.path(path, "Data/RFID", "test.csv") +rfid_file + +# Filter the simulated RFID data to pull out the first day of data collection +sim_dats_rfid %>% + dplyr::filter(day == 1) %>% + # Write out the filtered data frame as a .csv spreadsheet. Do not include row names + # Remember that the "." means the function will use the object that is piped in, which here is the data frame filtered to retain day 1 of data collection only + write.csv(x = ., file = rfid_file, row.names = FALSE) + +``` + +Check that the test file was created inside of the new RFID folder, and then delete it. +```{r eval = FALSE} + +list.files(file.path(path, "Data/RFID"), pattern = ".csv$") + +rfid_file <- file.path(path, "Data/RFID", "test.csv") +rfid_file + +file.remove(rfid_file) + +``` + +

Practicing writing a loop

+ +In order to write out a data frame for each day of data collection per sensor, you could repeat the code above 6 times (three times per sensor). But it's good to avoid repeating the same code over and over, since this can lead to messy scripts as well as a greater risk for errors in data processing and analysis. Whenever you need to run the same code many times, you can write a loop. Loops are an important coding skill and we'll work through building a loop step by step. + +To start, you can practice building a loop with the function `lapply()`. +```{r} + +?lapply + +# Make a vector of the files to write out +files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv")) + +files +length(files) + +``` + +Next, you can start writing out a looping structure. In the code below, the argument `X` specifies the number of times that the loop will run. In this case, `X` is a numeric vector from 1 to the length of the `files` vector and contains the numbers 1 and 2. Therefore the loop will run twice, and each value in `X` will be used in each loop iteration to write out one file at a time + +The argument `FUN` is a custom function, written using the `function(x){}` notation. All of the code inside of the curly brackets `{}` will be run for each iteration of the loop. The argument `x` inside `function()` is the iterating variable, or the variable that will take on a different value of the vector supplied to `X` in each iteration of the loop. +```{r} + +# In this loop, the iterating variable x will take on each value in the vector supplied to the X argument. This means that in the first loop iteration, x will take on the numeric value of 1. In the second loop iteration, x will hold the numeric value 2. To test that this is true, you can run the loop below, which will print the value of x per iteration to the console +lapply(X = 1:length(files), FUN = function(x){ + + x + +}) + +``` + +As you can see, the output of this loop is a list with 2 elements. Each list element is shown in double square brackets ([[1]] and [[2]]), and each list element contains a vector of length 1 that holds the value of the iterating variable in each iteration (1 and 2, respectively). + +The iterating variable of the loop, or `x`, does not exist as an object outside of the function. So if you print `x` outside of the loop, no object will be found. If you created a object called `x` outside of the loop above, then you will see the contents of that object instead. This means that writing a loop that uses `x` as an iterating variable will not affect other lines of code that use an object called `x` outside of the loop, and vice versa. + +The iterating variable of a function does not always need to be `x`, but can be another letter (`i`, `j`, `y`, `z`) or any combination of multiple letters and numbers, underscores, and periods (as long as the variable name starts with a letter). + +A useful property of the iterating variable is that you can use it to index vectors, data frames, or other objects that you created outside of the loop. For instance, you can use `x` and square bracket indexing to print the name of each file that you want to save: +```{r} + +lapply(X = 1:length(files), FUN = function(x){ + + files[x] + +}) + +``` + +You can also modify the code inside of the custom function to save each of these files, by placing the expression `files[x]` inside of the `write.csv()` function and piping a data frame to `write.csv()`. +```{r eval = FALSE} + +lapply(X = 1:length(files), FUN = function(x){ + + # In each iteration of the loop, you will save the data frame `sim_dats_rfid` as a separate spreadsheet with the file name specified in the given iteration + sim_dats_rfid %>% + write.csv(file = files[x], row.names = FALSE) + +}) + +``` + +You should see 2 "NULL" outputs printed to the console if the loop runs correctly. When you check the contents of the nested RFID folder, you should see that both of the testing .csv files were written out: +```{r} + +list.files(file.path(path, "Data/RFID")) + +``` + +You just wrote out 2 spreadsheets using 1 loop, but you wrote out the same data frame to each spreadsheet. In order to write out a different data frame to each spreadsheet, you can add the data frame filtering that you learned above to the loop. In the code below, you'll also create another vector object called `days`, and then use the iterating variable to filter `sim_dats_rfid` and write out a spreadsheet by each day in `days`. +```{r eval = FALSE} + +days <- c(1, 2, 3) + +lapply(X = 1:length(files), FUN = function(x){ + + sim_dats_rfid %>% + # Filter the data frame by one day at a time + dplyr::filter(day == days[x]) %>% + # Write out data frame filtered by the given day to a separate spreadsheet + write.csv(file = files[x], row.names = FALSE) + +}) + +``` + +You can delete these files that you created for testing. In the code below, you're seeing another pattern searching example while checking whether the function ran correctly. The string that you pass to the argument `pattern` starts with the symbol `^`, which is a symbol that indicates you're searching for all files that *start* with the pattern "test". You're also telling the function to return the full location of each file along with the file name, so that `file.remove()` knows exactly where to look for each file. +```{r eval = FALSE} + +rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE) +rem_files + +file.remove(rem_files) + +``` + +

Use a loop to save RFID spreadsheets

+ +Now you can put all of these pieces together and use the loop to write out a spreadsheet per day for the RFID sensor. +```{r eval = FALSE} + +# Make a vector of the custom file names to write out +files <- c( + "RFID_simulated_Pair-01_2023_08_01.csv", + "RFID_simulated_Pair-01_2023_08_02.csv", + "RFID_simulated_Pair-01_2023_08_03.csv" +) + +# Add the file path for the correct directory +files <- file.path(path, "Data/RFID", files) +files + +# Make a vector of the days to write out (1 day per iteration of the loop) +days <- c(1, 2, 3) +days + +# You can drop the lapply() argument names, since you're supplying the arguments in the order that the function expects +invisible(lapply(1:length(files), function(x){ + + sim_dats_rfid %>% + # Filter the data frame by one day at a time + dplyr::filter(day == days[x]) %>% + # Write out the filtered data frame to the correct spreadsheet for the given day + write.csv(file = files[x], row.names = FALSE) + +})) + +``` + +In the code above, you'll see one additional change that we made to the loop by wrapping it in the function `invisible()`. This function silences the output printed to the console that you saw several times above, in which the output of each `lapply()` iteration is enclosed in double square brackets, and then a single pair of square brackets. `lapply()` is a function that returns a list, and when the function is carried out correctly but there is not output to print, the function will return `NULL` values (indicating empty output). This is the expected behavior of `lapply()` for our purposes, because we used `lapply()` to write out physical files rather than to return output to the R console. Since you can check that the function worked by running `list.files()`, using `invisible()` helps clean up the amount of text that you need to check in your console. + +```{r eval = FALSE} + +# The new .csv files for each day of RFID data are present, looks good +list.files(file.path(path, "Data/RFID")) + +``` + +You can remove these files for now, since you will work on writing a nested loop structure that automatically writes out the data for each sensor type per day. +```{r eval = FALSE} + +files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv") + +# Add the file path for the correct directory +files <- file.path(path, "Data/RFID", files) +files + +file.remove(files) + +``` + +If you want more practice writing loops, you can write out a loop structure to save a spreadsheet for each day of data collection for the infrared beam breaker dataset. + +

Write a nested loop to save spreadsheets

+ +If you want to cut down on the amount of code that you're writing to save the raw data per sensor and day of data collection, you could write out both the RFID and beam breaker data in the same looping structure. To continue, you'll need to create another directory for the beam breaker data: +```{r eval = FALSE} + +dir.create(file.path(path, "Data", "IRBB")) + +``` + +To carry out the file filtering and writing for both sensor types across days, you'll use a type of object called a `list`. You will then use these lists inside of a nested loop structure: +```{r} + +# Make a list of the custom file names to write out for each sensor type and day +files <- list( + c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), + c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") +) + +glimpse(files) + +``` + +Lists are useful objects because they're very flexible. Unlike vectors, a single list can hold many different types of data. And then unlike a data frame, the elements of a list do not need to have the same dimensions. The elements of a list can also be different data structures or object types themselves. For instance, lists can contain vectors, data frames, and other lists all inside of the same larger list object. The list that you created above has 2 elements, and each element is a vector of 3 character strings containing the file names that you supplied using the function `c()`. + +You can index lists in a manner that is similar to indexing vectors and data frames, but indexing lists can be done with single or double square brackets for different outcomes: +```{r} + +# Using a single square bracket to filter a list returns the first list element in list format +files[1] +glimpse(files[1]) + +# Using double square brackets returns the first list element only, so it removes the list structure and shows the original data structure of that element (here a vector) +files[[1]] +glimpse(files[[1]]) + +``` + +Lists can also have named elements, which makes it possible to access elements by name. +```{r} + +# Make a named list of the custom file names to write out +files <- list( + `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), + `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") +) + +glimpse(files) + +``` + +Access the list elements by name: +```{r} + +# Using a single bracket returns the first element as a list, called "RFID" +files["RFID"] + +# Using the dollar sign or double square brackets returns the first list element in its original format +files$RFID + +files[["RFID"]] + +``` + +Lists are very useful data structures for setting up nested loop operations. For instance, if you want to write out a spreadsheet per day per sensor type, you need 1) a loop to iterate over sensor types, and then 2) a loop to iterate over days per sensor. You can use lists to create nested data structures to supply to a nested loop, which will help you make sure that each layer of the loop runs in the way that you expect. For instance, the list called `files` reflects the nested loop that you need because files are listed first by sensor type (each element of the list) and then by date (each element of the vector inside of each list element): +```{r} + +# Make a vector of sensor labels +sensors <- c("RFID", "IRBB") + +sensors + +# Make a named list of the custom file names to write out +files <- list( + `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"), + `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv") +) + +files + +# Make a list of file paths per sensor that will be used inside of the loop +file_dirs <- list( + `RFID` = file.path(path, "Data/RFID"), + `IRBB` = file.path(path, "Data/IRBB") +) + +file_dirs + +# Make a list of the days to write out for each sensor +# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor +days <- list( + `RFID` = c(1, 2, 3), + `IRBB` = c(1, 2, 3) +) + +days + +# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop. Here you can specify the data frame to use in the filtering operation per sensor type +dats <- list( + `RFID` = sim_dats_rfid, + `IRBB` = sim_dats_irbb +) + +glimpse(dats) + +``` + +Once you've set up the data structures that you want to loop over, you can write out the nested loop itself. Since this is a complex loop structure, it can be helpful to test the loop with set values of each iterating variable (below these will be `x` and `y`). + +After writing out this loop but before running the full loop structure, you should test the code inside of each loop layer. To do this, you can set the values of each iterating variable outside of the loop and then run the code inside of each loop. This form of testing is equivalent to freezing the loop in time, so that you're seeing the output of the code for a single loop iteration (below, the first iteration for each loop layer when `x` and `y` are both set to 1). + +To carry out this type of testing, you should run the code to freeze the iterating variables on the first iteration of each loop. Then, you should run the code inside of each loop, starting with the creation of `days_tmp`, then the indexing and filtering the data frame, and then the filtering of the file names. You should not run either of the lines with `lapply()` in them, since you don't want either loop to fully execute before you're sure that the inner code is working as expected. + +*Important note*: Above you learned that the iterating variables in a loop do not exist outside of the loop. In this testing that you will cary out, you're not fully running either loop, and so the code that you're testing inside of each loop will "see" the values of iterating variables that you initialized outside of the loops. + +As you test the code inside of each loop, you should see that between the outer and inner loops, you're using the name of the sensor for the given outer loop iteration ("RFID" for the first iteration) to set up the days over which the inner loop will iterate (in `days_tmp`). Inside of the inner loop, you're filtering the list of data frames by sensor, and then by the days that you want per sensor. Finally, you're indexing the file name for the right sensor type and day with a combination of double and single bracket filtering on the list of file names. Below, the lines of code that open and close each loop are commented out in order to guide you through which lines of code you should run in this testing step: +```{r eval = FALSE} + +# Freezer the iterating variables for testing +x <- 1 +y <- 1 + +# The outer loop: start by iterating over sensors +# invisible(lapply(1:length(sensors), function(x){ + + # Index the named list of days to get the right days per sensor + # This indexing is important to set up the next loop correctly + sensors[x] # This is a string with the sensor name + + # Place the string with the sensor name inside of double square brackets to extract the vector of days for that sensor + days_tmp <- days[[sensors[x]]] + + days_tmp + + # The inner loop: for each sensor, iterate over days + # lapply(1:length(days_tmp), function(y){ + + # Get the data frame per sensor type using x inside of double square brackets to extract the given data frame from the list + dats[[x]] %>% + # Filter the data frame by one day at a time by using y to index the temporary vector of days (e.g. to extract a single element from that vector) + dplyr::filter(day == days_tmp[y]) %>% + glimpse() + + # Use double bracket filtering to pull out the vector of file names for the given sensor from the overall list, then use y with single bracket filtering to pull a single file name from the resulting vector of names + files[[x]] + + files[[x]][y] + + # You'll also combine the file name with the right path: + file.path(file_dirs[[x]], files[[x]][y]) + + # }) + +# })) + +``` + +You should have a better sense now of how each loop operates over different data structures to carry out the operation that you want (writing out a single spreadsheet per sensor type and day). Next, you can modify the full loop to replace the lines written for testing with the final operations that you want to carry out: +```{r eval = FALSE} + +# Start by iterating over sensors +invisible(lapply(1:length(sensors), function(x){ + + # Index the list of days to get the right days per sensor + # This indexing is important to set up the next loop correctly + days_tmp <- days[[sensors[x]]] + + # For the given sensor, iterate over days of data collection to write out a spreadsheet per sensor and day + lapply(1:length(days_tmp), function(y){ + + # Get the data frame per sensor type using x and then filter by day using y + dats[[x]] %>% + # Filter the data frame by one day at a time + dplyr::filter(day == days_tmp[y]) %>% + # Use double-bracket filtering to pull out the right file path per sensor + # Then use double and single bracket filtering to pull out the right file name per sensor and day + write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE) + + }) + +})) + +``` + +This loop should have created 1 file per sensor and day in the correct directory per sensor. Check that these 6 files now exist inside each directory per sensor within your working directory. +```{r eval = FALSE} + +list.files(file.path(path, "Data/RFID")) + +list.files(file.path(path, "Data/IRBB")) + +``` + +You learned more about filtering data frames, writing them out to spreadsheets, and writing single-layer and nested loops in this vignette. In the next vignette, you'll use the spreadsheets of simulated RFID and IRBB data to start the ABISSMAL data processing and analysis workflow. \ No newline at end of file diff --git a/R/vignettes/Vignette_04_SaveData.html b/R/vignettes/Vignette_04_SaveData.html new file mode 100644 index 0000000..7ad397f --- /dev/null +++ b/R/vignettes/Vignette_04_SaveData.html @@ -0,0 +1,2472 @@ + + + + + + + + + + + + + + + +Vignette 04: Save Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this fourth vignette, you will write out spreadsheets of simulated +detections of animal movements to your computer. You will continue to +use coding skills that you learned in the previous vignettes, and you’ll +learn additional skills that include:

+
    +
  1. Indexing and filtering data frames
  2. +
  3. Checking data frame structure with base R and the tidyverse
  4. +
  5. Saving objects from R as physical files on your computer
  6. +
  7. Reading files from your computer into R
  8. +
  9. Writing and testing loops
  10. +
+

+Load packages and your working directory path +

+
rm(list = ls()) # Clean global environment
+
+library(tidyverse) # Load the set of tidyverse packages
+
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" # Initialize an object with the path that is your working directory
+

+Create the simulated data +

+

In the code below, you’ll recreate the simulated RFID and beam +breaker datasets that you learned to build in the previous vignette. +Here, the code from vignette 03 has been condensed into fewer +chunks:

+
# Create a vector of 4 RFID timestamps in HH:MM:SS format
+rfid_ts <- c("10:00:00", "10:05:00", "11:00:00", "11:05:00")
+
+# Add perching events to the RFID data
+rfid_ts <- c(rfid_ts, "08:00:00", "08:00:01", "08:00:02", "08:00:03", "11:30:00", "11:30:01", "11:30:02", "11:30:03", "11:30:04", "11:30:05")
+
+glimpse(rfid_ts)
+
##  chr [1:14] "10:00:00" "10:05:00" "11:00:00" "11:05:00" "08:00:00" ...
+
# Simulate outer and inner beam breaker timestamps for an entrance, an exit, and then another entrance and exit
+o_irbb_ts <- c("09:59:59", "10:05:01", "10:59:59", "11:05:01")
+i_irbb_ts <- c("10:00:01", "10:04:59", "11:00:01", "11:04:59")
+
+# Simulate some RFID detection failures across both beam breaker pairs
+# These RFID detection failures occurred over 2 additional simulated entrances and 2 additional simulated exits 
+o_irbb_ts <- c(o_irbb_ts, "06:05:05", "06:35:08", "07:15:40", "11:10:25")
+i_irbb_ts <- c(i_irbb_ts, "06:05:04", "06:35:09", "07:15:41", "11:10:24")
+
+# Simulate some stray detections for the outer beam breaker
+o_irbb_ts <- c(o_irbb_ts, "09:45:01", "09:45:02", "09:45:03", "09:45:04", "09:45:05", "09:45:06", "09:45:07", "09:45:08", "09:45:09", "09:45:10", "09:45:11")
+
+glimpse(o_irbb_ts)
+
##  chr [1:19] "09:59:59" "10:05:01" "10:59:59" "11:05:01" "06:05:05" ...
+
glimpse(i_irbb_ts)
+
##  chr [1:8] "10:00:01" "10:04:59" "11:00:01" "11:04:59" "06:05:04" ...
+

+Simulate 3 days of RFID data collection +

+

In the code below, you’ll combine the vector of RFID timestamps that +you made above with metadata in a data frame. This metadata will include +the year, month, and day, as well as a column with 2 unique PIT tag +identifiers (1 for each simulated individual), and a column with the +sensor type. You should recognize some of this code from the previous +vignette:

+
# Make a vector for the experimental replicate
+exp_rep <- rep(x = "Nest_01", times = length(rfid_ts))
+
+# Make a vector of the PIT tag codes
+# Allocate the first 4 RFID detections to the first individual, the first perching event (4 detections) to the first individual, and the second perching event (6 detections) to the second individual
+# The 3 rep() expressions are combined into a single vector using the c() function
+PIT_tag <- c(rep("1357aabbcc", 4), rep("1357aabbcc", 4), rep("2468zzyyxx", 6))
+
+# Make the data frame with the experimental replicate metadata and the timestamps
+sim_dats_rfid <- data.frame(chamber_id = exp_rep, timestamps = rfid_ts)
+
+# Overwrite the data frame with the modified version that has columns for the year, month, and day
+sim_dats_rfid <- sim_dats_rfid %>%
+  dplyr::mutate(
+    year = 2023
+  ) %>%
+  dplyr::mutate(
+    month = 08,
+    day = 01
+  ) %>%
+  # Add the PIT tag metadata as a new column
+  dplyr::mutate(
+    PIT_tag = PIT_tag
+  ) %>% 
+  dplyr::mutate(
+    sensor_id = "RFID"
+  )
+
+glimpse(sim_dats_rfid)
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

Next, you can use this data frame to simulate data collection over +more than one day. To create more observations (rows) for two additional +days, you can append rows from a modified copy of +sim_dats_rfid to itself.

+

In the code below, you’re piping sim_dats_rfid into +bind_rows(), which indicates that this is the original +object to which you want to append or add new rows. Then the code inside +of bind_rows() indicates the data frame (the new rows) that +will be appended to sim_dats_rfid. In this case, the code +inside of bind_rows() pipes sim_dats_rfid into +dplyr::mutate(), which is a function that you’re using to +modify the day column to reflect a subsequent day of data +collection. Then you’ll repeat this process to simulate a third day of +data collection:

+
sim_dats_rfid <- sim_dats_rfid %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  ) %>% 
+  bind_rows(
+    sim_dats_rfid %>% 
+      dplyr::mutate(
+        day = 03
+      )
+  )
+
+glimpse(sim_dats_rfid) # Triple the number of rows, looks good
+
## Rows: 42
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2,…
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+

+Check data frame columns with base R +

+

You checked that simulated data frame has data collected over three +days by looking at the structure of the new object. You can also check +the unique values present in the column day. Below is a way +of checking the unique values contained in a column of a data frame, +using two different examples of base R notation for accessing +columns:

+
# The name of a data frame object, followed by a $ sign and then the name of a column allows you to pull out one column at a time from a data frame. A data frame column is a vector, so when you run this code you will see a vector of values print to the console
+sim_dats_rfid$day
+
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
+## [39] 3 3 3 3
+
# You can also access a column in a data frame by using double square brackets after the name of the data frame, and placing the column name in quotes inside of the inner brackets
+sim_dats_rfid[["day"]]
+
##  [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3
+## [39] 3 3 3 3
+
# You can use the function unique() to see the unique values of any vector, including a column of a data frame
+unique(sim_dats_rfid$day) # Three days, looks good
+
## [1] 1 2 3
+
unique(sim_dats_rfid[["day"]]) # Three days, looks good
+
## [1] 1 2 3
+

+Check data frame columns with the tidyverse +

+

You can also check the unique values in a column using functions from +the tidyverse. In the expression below, you’re piping +sim_dats_rfid into the function pull(), which +lets you pull the column day out of the data frame as a +vector. Then that vector is piped into the function +unique() to check the unique values contained within it. +The function unique() does not need an argument inside of +the parentheses here because you’re already piping the output that it +needs directly into the function.

+
# Three days, looks good
+sim_dats_rfid %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2 3
+

+Simulate 3 days of beam breaker data collection +

+

Next, repeat this process of creating a data frame with metadata for +the infrared beam breaker datasets. Since the beam breakers do not +collect unique individual identity information, you will add columns for +the year, month, day, and sensor type. You will also simulate data +collection for the beam breakers over the same three days as the RFID +system.

+
# Overwrite the vector exp_rep with a new vector the same length as o_irbb_ts and i_irbb_ts together
+exp_rep <- rep(x = "Nest_01", times = length(o_irbb_ts) + length(i_irbb_ts))
+
+# Here the timestamps of both beam breaker pairs have been added to the same column using c()
+sim_dats_irbb <- data.frame(chamber_id = exp_rep, timestamps = c(o_irbb_ts, i_irbb_ts))
+
+sim_dats_irbb <- sim_dats_irbb %>%
+  dplyr::mutate(
+    year = 2023,
+    month = 08,
+    day = 01,
+    # Add a unique sensor identifier by beam breaker pair
+    # Each unique label is repeated for the length of the vector of timestamps of each beam breaker pair
+    sensor_id = c(rep("Outer Beam Breakers", length(o_irbb_ts)), rep("Inner Beam Breakers", length(i_irbb_ts)))
+  )
+
+glimpse(sim_dats_irbb)
+
## Rows: 27
+## Columns: 6
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "09:59:59", "10:05:01", "10:59:59", "11:05:01", "06:05:05",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
+## $ sensor_id  <chr> "Outer Beam Breakers", "Outer Beam Breakers", "Outer Beam B…
+
sim_dats_irbb <- sim_dats_irbb %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 02
+      )
+  ) %>% 
+  bind_rows(
+    sim_dats_irbb %>% 
+      dplyr::mutate(
+        day = 03
+      )
+  )
+
+glimpse(sim_dats_irbb) # Triple the number of rows, looks good
+
## Rows: 81
+## Columns: 6
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "09:59:59", "10:05:01", "10:59:59", "11:05:01", "06:05:05",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,…
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
+## $ sensor_id  <chr> "Outer Beam Breakers", "Outer Beam Breakers", "Outer Beam B…
+
# Three days, looks good
+sim_dats_irbb %>% 
+  pull(day) %>% 
+  unique()
+
## [1] 1 2 3
+

+Save a data frame as a physical file +

+

The data frames that you create and manipulate in R can be saved as +physical files in your working directory. You have many different file +type options for saving data frames, but I recommend using .csv format +since this file type is compatible with R, Microsoft Word, and other +software. You can use the function write.csv() to save data +frames to .csv spreadsheets on your computer:

+
?write.csv
+

In order to write a physical file to your working directory, you need +to tell R 1) where to save the file and 2) the name of the file that you +want to create. You can pass both of these pieces of information to +write.csv() by combining your working directory path and +the file name through the function file.path(). For this +example, you’ll create a test file as you practice using +write.csv():

+
# Create a custom file name by combining the path for your working directory with the file name that you want to write out
+# The function file.path() will combine both pieces of information into a single file path
+rfid_file <- file.path(path, "test_file.csv")
+
+# This object contains the location where the file will be saved, followed by the file name
+rfid_file
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/test_file.csv"
+

Next, you can pipe the data frame to write.csv() and +specify additional information for creating the .csv file, such as +whether you want to add a column of numeric row identifiers:

+
sim_dats_rfid %>% 
+  # Write out the data frame as a .csv spreadsheet. Do not include row names
+  #The "." below means that the function write.csv() will operate on the object that is piped in, which here is the data frame sim_dats_rfid
+  write.csv(x = ., file = rfid_file, row.names = FALSE)
+

As specified in the function documentation for +write.csv(), the function will include column names in the +resulting spreadsheet by default. The function will also not append new +information to the .csv if it already exists, so if you already created +this file then it will be overwritten when you run the function +again.

+

Next, you can check that write.csv() worked as expected +by using list.files() to check the files in your working +directory.

+
# List all files in the given path
+list.files(path)
+

You can also use list.files() to customize a search with +the argument called pattern. Using the argument +pattern here is similar to searching for a specific word +inside of a text document. The dollar sign placed after “.csv” means +that you’re telling the function to search specifically for all files +that end in the pattern “.csv”:

+
# List only files that end in the pattern ".csv" in the given path
+list.files(path, pattern = ".csv$")
+

+Read in a spreadsheet +

+

You can read one these files back into R with the function +read.csv(). In the code below, you’re piping the output of +read.csv() directly into glimpse() to check +out the structure of the resulting data frame. The output of the code is +printed to the console, but is not saved inside of an object.

+
read.csv(file.path(path, "test_file.csv")) %>% 
+  glimpse()
+

Now that you’ve practiced using write.csv() and +read.csv(), you can delete the temporary file that you +created by feeding the rfid_file object to the function +file.remove().

+
rfid_file <- file.path(path, "test_file.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Save simulated data for the ABISSMAL workflow +

+

In the code below, you’ll work through a pipeline to save the +simulated data in the format and locations expected by the custom +ABISSMAL functions. In order to use the ABISSMAL pipeline, the simulated +raw data needs to be saved in a separate spreadsheet per sensor and day +of data collection. These spreadsheets need to be saved inside of a +folder per sensor as well. ABISSMAL will automatically save data in +these ways when the tracking system is used to collect data from live +animals.

+

+Filter a data frame +

+

In the code below, you’ll practice how to use the function +dplyr::filter() to filter rows of a data frame by day and +then save a filtered data frame with write.csv(). To filter +a data frame, you can use a conditional statement inside of the function +dplyr::filter():

+
# Pipe the data frame into the filter() function
+sim_dats_rfid %>% 
+  # Filter the data frame by pulling out all rows in which the day column was equal to 1
+  dplyr::filter(day == 1) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+
# Check that the filtering was done correctly by getting the unique values in the day column. Looks good
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1
+

You can obtain similar results by inverting the conditional statement +inside of dplyr::filter() to remove days that are +not the second or third days of data collection. Below +you added two conditional statements together using the symbol +“&”.

+
sim_dats_rfid %>% 
+  # Filter the data frame by pulling out all rows in which the day column was not equal to 2
+  dplyr::filter(day != 2 & day != 3) %>%
+  glimpse()
+
## Rows: 14
+## Columns: 7
+## $ chamber_id <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nes…
+## $ timestamps <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08:00:00",…
+## $ year       <dbl> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023,…
+## $ month      <dbl> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+## $ day        <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
+## $ PIT_tag    <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabbcc", "13…
+## $ sensor_id  <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+
# Check that the filtering was done correctly by getting the unique values in the day column. Looks good
+sim_dats_rfid %>% 
+  dplyr::filter(day != 2 & day != 3) %>%
+  pull(day) %>% 
+  unique()
+
## [1] 1
+

Now that you’ve practiced filtering a data frame, you can combine +this filtering step with writing out a .csv file that contains the +filtered data frame with data collected on a single day:

+
# Make a new directory inside of your working directory for saving data
+file.path(path, "Data") # Check the new path
+dir.create(file.path(path, "Data")) # Create the new path
+
+# Then make a new directory inside of the Data folder for the raw RFID data
+file.path(path, "RFID") # Check the new path
+dir.create(file.path(path, "Data", "RFID")) # Create the new path
+
+# Make the new file name with your working directory path
+# Make sure to specify that the spreadsheet will be saved inside of the new folder "RFID"
+rfid_file <- file.path(path, "Data/RFID", "test.csv")
+rfid_file
+
+# Filter the simulated RFID data to pull out the first day of data collection
+sim_dats_rfid %>% 
+  dplyr::filter(day == 1) %>% 
+  # Write out the filtered data frame as a .csv spreadsheet. Do not include row names
+  # Remember that the "." means the function will use the object that is piped in, which here is the data frame filtered to retain day 1 of data collection only
+  write.csv(x = ., file = rfid_file, row.names = FALSE)
+

Check that the test file was created inside of the new RFID folder, +and then delete it.

+
list.files(file.path(path, "Data/RFID"), pattern = ".csv$")
+
+rfid_file <- file.path(path, "Data/RFID", "test.csv")
+rfid_file
+
+file.remove(rfid_file)
+

+Practicing writing a loop +

+

In order to write out a data frame for each day of data collection +per sensor, you could repeat the code above 6 times (three times per +sensor). But it’s good to avoid repeating the same code over and over, +since this can lead to messy scripts as well as a greater risk for +errors in data processing and analysis. Whenever you need to run the +same code many times, you can write a loop. Loops are an important +coding skill and we’ll work through building a loop step by step.

+

To start, you can practice building a loop with the function +lapply().

+
?lapply
+
+# Make a vector of the files to write out
+files <- c(file.path(path, "Data/RFID", "test1.csv"), file.path(path, "Data/RFID", "test2.csv"))
+
+files
+
## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test1.csv"
+## [2] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test2.csv"
+
length(files)
+
## [1] 2
+

Next, you can start writing out a looping structure. In the code +below, the argument X specifies the number of times that +the loop will run. In this case, X is a numeric vector from +1 to the length of the files vector and contains the +numbers 1 and 2. Therefore the loop will run twice, and each value in +X will be used in each loop iteration to write out one file +at a time

+

The argument FUN is a custom function, written using the +function(x){} notation. All of the code inside of the curly +brackets {} will be run for each iteration of the loop. The +argument x inside function() is the iterating +variable, or the variable that will take on a different value of the +vector supplied to X in each iteration of the loop.

+
# In this loop, the iterating variable x will take on each value in the vector supplied to the X argument. This means that in the first loop iteration, x will take on the numeric value of 1. In the second loop iteration, x will hold the numeric value 2. To test that this is true, you can run the loop below, which will print the value of x per iteration to the console
+lapply(X = 1:length(files), FUN = function(x){
+  
+  x
+  
+})
+
## [[1]]
+## [1] 1
+## 
+## [[2]]
+## [1] 2
+

As you can see, the output of this loop is a list with 2 elements. +Each list element is shown in double square brackets ([[1]] and [[2]]), +and each list element contains a vector of length 1 that holds the value +of the iterating variable in each iteration (1 and 2, respectively).

+

The iterating variable of the loop, or x, does not exist +as an object outside of the function. So if you print x +outside of the loop, no object will be found. If you created a object +called x outside of the loop above, then you will see the +contents of that object instead. This means that writing a loop that +uses x as an iterating variable will not affect other lines +of code that use an object called x outside of the loop, +and vice versa.

+

The iterating variable of a function does not always need to be +x, but can be another letter (i, +j, y, z) or any combination of +multiple letters and numbers, underscores, and periods (as long as the +variable name starts with a letter).

+

A useful property of the iterating variable is that you can use it to +index vectors, data frames, or other objects that you created outside of +the loop. For instance, you can use x and square bracket +indexing to print the name of each file that you want to save:

+
lapply(X = 1:length(files), FUN = function(x){
+  
+  files[x]
+  
+})
+
## [[1]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test1.csv"
+## 
+## [[2]]
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID/test2.csv"
+

You can also modify the code inside of the custom function to save +each of these files, by placing the expression files[x] +inside of the write.csv() function and piping a data frame +to write.csv().

+
lapply(X = 1:length(files), FUN = function(x){
+  
+  # In each iteration of the loop, you will save the data frame `sim_dats_rfid` as a separate spreadsheet with the file name specified in the given iteration
+  sim_dats_rfid %>%
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

You should see 2 “NULL” outputs printed to the console if the loop +runs correctly. When you check the contents of the nested RFID folder, +you should see that both of the testing .csv files were written out:

+
list.files(file.path(path, "Data/RFID"))
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+

You just wrote out 2 spreadsheets using 1 loop, but you wrote out the +same data frame to each spreadsheet. In order to write out a different +data frame to each spreadsheet, you can add the data frame filtering +that you learned above to the loop. In the code below, you’ll also +create another vector object called days, and then use the +iterating variable to filter sim_dats_rfid and write out a +spreadsheet by each day in days.

+
days <- c(1, 2, 3)
+
+lapply(X = 1:length(files), FUN = function(x){
+  
+  sim_dats_rfid %>%
+    # Filter the data frame by one day at a time
+    dplyr::filter(day == days[x]) %>%
+    # Write out data frame filtered by the given day to a separate spreadsheet 
+    write.csv(file = files[x], row.names = FALSE)
+  
+})
+

You can delete these files that you created for testing. In the code +below, you’re seeing another pattern searching example while checking +whether the function ran correctly. The string that you pass to the +argument pattern starts with the symbol ^, +which is a symbol that indicates you’re searching for all files that +start with the pattern “test”. You’re also telling the function +to return the full location of each file along with the file name, so +that file.remove() knows exactly where to look for each +file.

+
rem_files <- list.files(file.path(path, "Data/RFID"), pattern = "^test", full.names = TRUE)
+rem_files
+
+file.remove(rem_files)
+

+Use a loop to save RFID spreadsheets +

+

Now you can put all of these pieces together and use the loop to +write out a spreadsheet per day for the RFID sensor.

+
# Make a vector of the custom file names to write out
+files <- c(
+  "RFID_simulated_Pair-01_2023_08_01.csv", 
+  "RFID_simulated_Pair-01_2023_08_02.csv", 
+  "RFID_simulated_Pair-01_2023_08_03.csv"
+)
+
+# Add the file path for the correct directory
+files <- file.path(path, "Data/RFID", files) 
+files
+
+# Make a vector of the days to write out (1 day per iteration of the loop)
+days <- c(1, 2, 3)
+days
+
+# You can drop the lapply() argument names, since you're supplying the arguments in the order that the function expects
+invisible(lapply(1:length(files), function(x){
+  
+  sim_dats_rfid %>%
+    # Filter the data frame by one day at a time
+    dplyr::filter(day == days[x]) %>%
+    # Write out the filtered data frame to the correct spreadsheet for the given day
+    write.csv(file = files[x], row.names = FALSE)
+  
+}))
+

In the code above, you’ll see one additional change that we made to +the loop by wrapping it in the function invisible(). This +function silences the output printed to the console that you saw several +times above, in which the output of each lapply() iteration +is enclosed in double square brackets, and then a single pair of square +brackets. lapply() is a function that returns a list, and +when the function is carried out correctly but there is not output to +print, the function will return NULL values (indicating +empty output). This is the expected behavior of lapply() +for our purposes, because we used lapply() to write out +physical files rather than to return output to the R console. Since you +can check that the function worked by running list.files(), +using invisible() helps clean up the amount of text that +you need to check in your console.

+
# The new .csv files for each day of RFID data are present, looks good
+list.files(file.path(path, "Data/RFID"))
+

You can remove these files for now, since you will work on writing a +nested loop structure that automatically writes out the data for each +sensor type per day.

+
files <- c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv")
+
+# Add the file path for the correct directory
+files <- file.path(path, "Data/RFID", files) 
+files
+
+file.remove(files)
+

If you want more practice writing loops, you can write out a loop +structure to save a spreadsheet for each day of data collection for the +infrared beam breaker dataset.

+

+Write a nested loop to save spreadsheets +

+

If you want to cut down on the amount of code that you’re writing to +save the raw data per sensor and day of data collection, you could write +out both the RFID and beam breaker data in the same looping structure. +To continue, you’ll need to create another directory for the beam +breaker data:

+
dir.create(file.path(path, "Data", "IRBB"))
+

To carry out the file filtering and writing for both sensor types +across days, you’ll use a type of object called a list. You +will then use these lists inside of a nested loop structure:

+
# Make a list of the custom file names to write out for each sensor type and day
+files <- list(
+  c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ : chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+##  $ : chr [1:3] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.csv" "IRBB_simulated_Pair-01_2023_08_03.csv"
+

Lists are useful objects because they’re very flexible. Unlike +vectors, a single list can hold many different types of data. And then +unlike a data frame, the elements of a list do not need to have the same +dimensions. The elements of a list can also be different data structures +or object types themselves. For instance, lists can contain vectors, +data frames, and other lists all inside of the same larger list object. +The list that you created above has 2 elements, and each element is a +vector of 3 character strings containing the file names that you +supplied using the function c().

+

You can index lists in a manner that is similar to indexing vectors +and data frames, but indexing lists can be done with single or double +square brackets for different outcomes:

+
# Using a single square bracket to filter a list returns the first list element in list format
+files[1]
+
## [[1]]
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
glimpse(files[1])
+
## List of 1
+##  $ : chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+
# Using double square brackets returns the first list element only, so it removes the list structure and shows the original data structure of that element (here a vector)
+files[[1]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
glimpse(files[[1]])
+
##  chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" ...
+

Lists can also have named elements, which makes it possible to access +elements by name.

+
# Make a named list of the custom file names to write out
+files <- list(
+  `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+glimpse(files)
+
## List of 2
+##  $ RFID: chr [1:3] "RFID_simulated_Pair-01_2023_08_01.csv" "RFID_simulated_Pair-01_2023_08_02.csv" "RFID_simulated_Pair-01_2023_08_03.csv"
+##  $ IRBB: chr [1:3] "IRBB_simulated_Pair-01_2023_08_01.csv" "IRBB_simulated_Pair-01_2023_08_02.csv" "IRBB_simulated_Pair-01_2023_08_03.csv"
+

Access the list elements by name:

+
# Using a single bracket returns the first element as a list, called "RFID"
+files["RFID"]
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
# Using the dollar sign or double square brackets returns the first list element in its original format
+files$RFID
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+
files[["RFID"]]
+
## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+

Lists are very useful data structures for setting up nested loop +operations. For instance, if you want to write out a spreadsheet per day +per sensor type, you need 1) a loop to iterate over sensor types, and +then 2) a loop to iterate over days per sensor. You can use lists to +create nested data structures to supply to a nested loop, which will +help you make sure that each layer of the loop runs in the way that you +expect. For instance, the list called files reflects the +nested loop that you need because files are listed first by sensor type +(each element of the list) and then by date (each element of the vector +inside of each list element):

+
# Make a vector of sensor labels
+sensors <- c("RFID", "IRBB")
+
+sensors
+
## [1] "RFID" "IRBB"
+
# Make a named list of the custom file names to write out
+files <- list(
+  `RFID` = c("RFID_simulated_Pair-01_2023_08_01.csv", "RFID_simulated_Pair-01_2023_08_02.csv", "RFID_simulated_Pair-01_2023_08_03.csv"),
+  `IRBB` = c("IRBB_simulated_Pair-01_2023_08_01.csv", "IRBB_simulated_Pair-01_2023_08_02.csv", "IRBB_simulated_Pair-01_2023_08_03.csv")
+)
+
+files
+
## $RFID
+## [1] "RFID_simulated_Pair-01_2023_08_01.csv"
+## [2] "RFID_simulated_Pair-01_2023_08_02.csv"
+## [3] "RFID_simulated_Pair-01_2023_08_03.csv"
+## 
+## $IRBB
+## [1] "IRBB_simulated_Pair-01_2023_08_01.csv"
+## [2] "IRBB_simulated_Pair-01_2023_08_02.csv"
+## [3] "IRBB_simulated_Pair-01_2023_08_03.csv"
+
# Make a list of file paths per sensor that will be used inside of the loop
+file_dirs <- list(
+  `RFID` = file.path(path, "Data/RFID"),
+  `IRBB` = file.path(path, "Data/IRBB") 
+)
+
+file_dirs
+
## $RFID
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/RFID"
+## 
+## $IRBB
+## [1] "/home/gsvidaurre/Desktop/ABISSMAL_vignettes/Data/IRBB"
+
# Make a list of the days to write out for each sensor
+# This could be a single vector rather than a list since you want to write out the same days per sensor, but a list is useful in case you wanted to change the days (and number of days) to write out per sensor
+days <- list(
+  `RFID` = c(1, 2, 3),
+  `IRBB` = c(1, 2, 3)
+)
+
+days
+
## $RFID
+## [1] 1 2 3
+## 
+## $IRBB
+## [1] 1 2 3
+
# Finally, you need to make a list of the data frames that you want to write out. Since you already know how to filter data frames by day, that code can go inside of the loop. Here you can specify the data frame to use in the filtering operation per sensor type
+dats <- list(
+  `RFID` = sim_dats_rfid,
+  `IRBB` = sim_dats_irbb
+)
+
+glimpse(dats)
+
## List of 2
+##  $ RFID:'data.frame':    42 obs. of  7 variables:
+##   ..$ chamber_id: chr [1:42] "Nest_01" "Nest_01" "Nest_01" "Nest_01" ...
+##   ..$ timestamps: chr [1:42] "10:00:00" "10:05:00" "11:00:00" "11:05:00" ...
+##   ..$ year      : num [1:42] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:42] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:42] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ PIT_tag   : chr [1:42] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" ...
+##   ..$ sensor_id : chr [1:42] "RFID" "RFID" "RFID" "RFID" ...
+##  $ IRBB:'data.frame':    81 obs. of  6 variables:
+##   ..$ chamber_id: chr [1:81] "Nest_01" "Nest_01" "Nest_01" "Nest_01" ...
+##   ..$ timestamps: chr [1:81] "09:59:59" "10:05:01" "10:59:59" "11:05:01" ...
+##   ..$ year      : num [1:81] 2023 2023 2023 2023 2023 ...
+##   ..$ month     : num [1:81] 8 8 8 8 8 8 8 8 8 8 ...
+##   ..$ day       : num [1:81] 1 1 1 1 1 1 1 1 1 1 ...
+##   ..$ sensor_id : chr [1:81] "Outer Beam Breakers" "Outer Beam Breakers" "Outer Beam Breakers" "Outer Beam Breakers" ...
+

Once you’ve set up the data structures that you want to loop over, +you can write out the nested loop itself. Since this is a complex loop +structure, it can be helpful to test the loop with set values of each +iterating variable (below these will be x and +y).

+

After writing out this loop but before running the full loop +structure, you should test the code inside of each loop layer. To do +this, you can set the values of each iterating variable outside of the +loop and then run the code inside of each loop. This form of testing is +equivalent to freezing the loop in time, so that you’re seeing the +output of the code for a single loop iteration (below, the first +iteration for each loop layer when x and y are +both set to 1).

+

To carry out this type of testing, you should run the code to freeze +the iterating variables on the first iteration of each loop. Then, you +should run the code inside of each loop, starting with the creation of +days_tmp, then the indexing and filtering the data frame, +and then the filtering of the file names. You should not run either of +the lines with lapply() in them, since you don’t want +either loop to fully execute before you’re sure that the inner code is +working as expected.

+

Important note: Above you learned that the iterating +variables in a loop do not exist outside of the loop. In this testing +that you will cary out, you’re not fully running either loop, and so the +code that you’re testing inside of each loop will “see” the values of +iterating variables that you initialized outside of the loops.

+

As you test the code inside of each loop, you should see that between +the outer and inner loops, you’re using the name of the sensor for the +given outer loop iteration (“RFID” for the first iteration) to set up +the days over which the inner loop will iterate (in +days_tmp). Inside of the inner loop, you’re filtering the +list of data frames by sensor, and then by the days that you want per +sensor. Finally, you’re indexing the file name for the right sensor type +and day with a combination of double and single bracket filtering on the +list of file names. Below, the lines of code that open and close each +loop are commented out in order to guide you through which lines of code +you should run in this testing step:

+
# Freezer the iterating variables for testing
+x <- 1
+y <- 1
+
+# The outer loop: start by iterating over sensors
+# invisible(lapply(1:length(sensors), function(x){
+  
+  # Index the named list of days to get the right days per sensor
+  # This indexing is important to set up the next loop correctly
+  sensors[x] # This is a string with the sensor name
+  
+  # Place the string with the sensor name inside of double square brackets to extract the vector of days for that sensor 
+  days_tmp <- days[[sensors[x]]]
+  
+  days_tmp
+  
+  # The inner loop: for each sensor, iterate over days
+  # lapply(1:length(days_tmp), function(y){
+    
+    # Get the data frame per sensor type using x inside of double square brackets to extract the given data frame from the list
+    dats[[x]] %>%
+      # Filter the data frame by one day at a time by using y to index the temporary vector of days (e.g. to extract a single element from that vector)
+      dplyr::filter(day == days_tmp[y]) %>% 
+      glimpse()
+    
+    # Use double bracket filtering to pull out the vector of file names for the given sensor from the overall list, then use y with single bracket filtering to pull a single file name from the resulting vector of names
+    files[[x]]
+    
+    files[[x]][y]
+    
+    # You'll also combine the file name with the right path:
+    file.path(file_dirs[[x]], files[[x]][y])
+    
+  # })
+  
+# }))
+

You should have a better sense now of how each loop operates over +different data structures to carry out the operation that you want +(writing out a single spreadsheet per sensor type and day). Next, you +can modify the full loop to replace the lines written for testing with +the final operations that you want to carry out:

+
# Start by iterating over sensors
+invisible(lapply(1:length(sensors), function(x){
+  
+  # Index the list of days to get the right days per sensor
+  # This indexing is important to set up the next loop correctly
+  days_tmp <- days[[sensors[x]]]
+  
+  # For the given sensor, iterate over days of data collection to write out a spreadsheet per sensor and day
+  lapply(1:length(days_tmp), function(y){
+    
+    # Get the data frame per sensor type using x and then filter by day using y
+    dats[[x]] %>%
+      # Filter the data frame by one day at a time
+      dplyr::filter(day == days_tmp[y]) %>%
+      # Use double-bracket filtering to pull out the right file path per sensor
+      # Then use double and single bracket filtering to pull out the right file name per sensor and day
+      write.csv(file = file.path(file_dirs[[x]], files[[x]][y]), row.names = FALSE)
+    
+  })
+  
+}))
+

This loop should have created 1 file per sensor and day in the +correct directory per sensor. Check that these 6 files now exist inside +each directory per sensor within your working directory.

+
list.files(file.path(path, "Data/RFID"))
+
+list.files(file.path(path, "Data/IRBB"))
+

You learned more about filtering data frames, writing them out to +spreadsheets, and writing single-layer and nested loops in this +vignette. In the next vignette, you’ll use the spreadsheets of simulated +RFID and IRBB data to start the ABISSMAL data processing and analysis +workflow.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd new file mode 100644 index 0000000..93f4cb9 --- /dev/null +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.Rmd @@ -0,0 +1,499 @@ +--- +title: "Vignette 05: Process and Plot Data" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette overview and learning objectives

+ +In this fifth vignette, you will begin using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow, including combining raw data across days and pre-processing the raw data. You will also make visualizations of the processed data. You will continue to use coding skills that you learned in the previous vignettes, and you will learn additional skills that include: + +1. Accessing custom functions +2. Using custom functions +3. Making graphs with `ggplot` + +

Load packages and your working directory path

+ +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Clean global environment + +library(tidyverse) # Load the set of tidyverse packages +library(data.table) # Load other packages that the ABISSMAL functions require + +# Initialize an object with the path that is your working directory +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" + +``` + +

Load ABISSMAL functions

+ +The custom R functions available through ABISSMAL are stored in physical files (extension .R) inside the local repository on your computer (which you should have downloaded in vignette 01). In order to start using the ABISSMAL functions, you need to load the physical .R files so that the functions are available in your global environment. In the code below, you will use the function `source()` to load 3 of the 5 main ABISSMAL functions, plus a script that holds a set of utility functions: +```{r} + +# Load the function that combines raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R") + +# Load the function that detects perching events in the raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R") + +# Load the function that pre-processes raw data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R") + +# Load a script with utility functions that each function above requires +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +

Access ABISSMAL function information

+ +After running the lines of code above, you should see that a whole set of functions have been loaded into your global environment (check the `Environment` pane). Many of these functions start with `check_`, and those are utility functions. If you scroll down, you'll see that the three of the main ABISSMAL functions above (`combine_raw_data`, `detect_perching_events`, `preprocess_detections`) are all loaded in your global environment as well. In the column to the right of the function names you can also see a preview of each function's arguments. + +To get more information about each of these three main functions, you can click the white square icon to the very right of each function in the `Environment` pane, or run the code `View(function_name)`. This will open the script for the given function a new tab in your Source pane. In each script for each function, you'll see lines of documentation starting with the symbols "`# @". You'll see the function name and description first, and then a description of each argument (parameter) for the function. If you keep scrolling down, you'll see a section with details about how the given function works, and the information that it returns. After the lines of documentation, you'll see the code that makes up the function itself. + +

Combine raw data

+ +Once you've loaded the ABISSMAL functions, you can start using the first function, `combine_raw_data()`, to combine data collected across days per sensor into a single spreadsheet per sensor. You'll start by combining raw data for the RFID sensor collected over different days into a single spreadsheet. + +Here goes more information about the arguments that you are supplying to the `combine_raw_data()` function below: + +* `sensors` is a vector containing the labels of the sensors for which you want to combine raw data. Below you're specifying RFID as a single sensor + +* `path` is your general working directory + +* `data_dir` is the folder that holds data inside of your working directory + +* `out_dir` is the folder where you want to save the combined raw data spreadsheet. The function will create this folder if it does not already exist + +* `tz` is the timezone for converting timestamps to POSIXct format. The default is "America/New York", and you can check out the "Time zones" section in the documentation for DateTimeClasses in R for more information (`?DateTimeClasses`) + +* `POSIXct_format` is a string containing the POSIX formatting information for how dates and timestamps should be combined into a single column. The default is to encode the year as a 4 digit number and the month and day as 2 digit numbers, separated by dashes. The date is followed by a space, then the 2-digit hour, minute, and decimal second (separated by colons) +```{r} + +# Combine raw data for the RFID and infrared beam breaker sensors separately +combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +You can check that `combine_raw_data()` wrote a spreadsheet of raw RFID data to the new directory `raw_combined`: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +You can read the combined .csv file (called "combined_raw_data_RFID.csv") back into R to check out the structure of this spreadsheet: +```{r} + +rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) + +glimpse(rfid_data) + +``` + +Reading this spreadsheet back into R created a data frame object. You should be able to see that there are some new columns created by the function, such as the column `data_type`. For this spreadsheet, the columns `sensor_id` and `data_type` contain the same information, but it's useful to have separate columns to keep track of the sensor id and type of sensor used to collect data when multiple sensors are used (e.g. two beam breaker pairs will each have a unique identifier in the `sensor_id` column). + +The function `combine_raw_data()` also created a new timestamps column in POSIXct format for downsteam processing and analysis, but it kept the original timestamps column. The function then added columns to indicate the stage of data processing and the date that the raw data were combined. Finally, if you check the folders with the original raw RFID data, you'll see that the original spreadsheets per day were retained and were not overwritten. + +You can also run `combine_raw_data()` with raw data from multiple sensors by supplying a vector with the sensor labels to the argument `sensors`. The function will still combine the raw data per sensor into separate spreadsheets per sensor, and in the process, you can avoid copy-pasting the code multiple times to run this process for multiple sensors: +```{r} + +combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +The RFID data will be overwritten, and you should see an additional spreadsheet with the raw IRBB data: +```{r} + +list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$") + +``` + +

Detect perching events

+ +You can use the combined raw data from different sensors in subsequent ABISSMAL functions to start making behavioral inferences from the movement detection datasets. For instance, you can detect perching events in the raw RFID data with the function `detect_perching_events()`. You can read more about each argument in the R script that contains this function. +```{r} + +detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +`detect_perching_events()` can only operate on a single file and sensor type at a time. The function will automatically create a new folder called "processed", and will save the .csv inside of that folder with perching events (if these were detected using the temporal threshold and run length above). + +When we simulated data in the third and fourth vignettes, you simulated perching events in the RFID dataset only. Did the code above recover those perching events? +```{r} + +perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv")) + +glimpse(perching) + +``` + +`detect_perching_events()` identified 6 total perching events, which is the same number that you simulated in the previous vignette (two perching events per day). You can look at the values inside of data frame for more information about these perching events: +```{r} + +# The timestamps when each perching event started +perching$perching_start + +# The timestamps when each perching event ended +perching$perching_end + +# The unique PIT tag identifier that tells you which individual was perched on the RFID antenna +perching$PIT_tag + +``` + +You can also view the whole data frame in a separate pane: +```{r eval = FALSE} + +View(perching) + +``` + +The information above tells you that there were 2 perching events detected at 8:00 each day, and 2 perching events detected at 11:30 each day (as expected). The PIT tag for each individual was detected once each day, so there was 1 perching event performed by each individual on each day. + +Detecting perching events is not a requirement in the ABISSMAL workflow, but it can be a useful step to obtain as much information from the raw data before some detections are dropped during pre-processing (see below). + +

Pre-process raw data

+ +Once you've detected perching events in the raw data, you can move on to pre-processing the raw data itself with `preprocess_detections()`. The raw data can sometimes contain multiple detections separated by a short period of time (e.g. RFID detections when an individual is perching on the antenna), and these multiple detections can be a source of noise when you're trying to make behavioral inferences across data collected by multiple sensors. When the argument `mode` is set to "thin", `preprocess_detections()` removes detections separated by a very short period of time (determined by the temporal threshold in seconds passed to the argument `thin_threshold`), and retains a reduced datasets of detections that still represents discrete movement events. + +`preprocess_detections()` also operates on a single sensor type at a time: +```{r} + +preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +You should now see an additional .csv file called "pre_processed_data_RFID.csv" in the "processed" folder: +```{r} + +list.files(file.path(path, "Data/processed")) + +``` + +You can read this file into R to check out the data structure. You should see that there are fewer rows here compared to the spreadsheet of raw data: +```{r} + +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) + +glimpse(rfid_pp) + +``` + +Next, you can pre-process the raw beam breaker data and check out the resulting .csv file: +```{r} + +preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +list.files(file.path(path, "Data/processed")) + +irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv")) + +glimpse(irbb_pp) + +``` + +

Plot RFID data in a barcode plot

+ +Now that you've combined and processed the raw data per sensor, it's time to visualize these different datasets. Making visualizations as you write code is important for generating high-quality figures for publications and presentations, but also for checking your work. + +In the code below, you'll learn how to use functions from the `ggplot2` package to make a barcode style figure of the raw and pre-processed RFID detection datasets. + +Start by reading in the raw and pre-processed RFID data, as well as the RFID perching events, and convert the timestamps to POSIX format. +```{r} + +rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>% + # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + # Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order (less to more recent) + dplyr::arrange(-desc(timestamp_ms)) + +# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly +glimpse(rfid_raw) + +rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>% + # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + dplyr::arrange(-desc(timestamp_ms)) + +glimpse(rfid_pp) + +rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>% + # The start and end timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), + perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + dplyr::arrange(-desc(perching_start)) + +glimpse(rfid_perch) + +``` + +Next, you can combine the original and pre-processed datasets into a single data frame to facilitate combining them in the same plot. You will add a new column (`dataset`) with labels in order to identify the two different datasets. +```{r} + +rfid_combined <- rfid_raw %>% + dplyr::select(sensor_id, day, timestamp_ms) %>% + dplyr::mutate( + dataset = "raw" + ) %>% + bind_rows( + rfid_pp %>% + dplyr::select(sensor_id, day, timestamp_ms) %>% + dplyr::mutate( + dataset = "pre-processed" + ) + ) %>% + dplyr::arrange(-desc(timestamp_ms)) + +glimpse(rfid_combined) + +``` + +You will start plotting the data using functions from `ggplot2`. This package is part of the `tidyverse`, but can also be installed and used separately. You can check out this [link](https://ggplot2.tidyverse.org/) for more resources to learn how to use ggplot notation to make different types of plots. These resources include sections of three different books with hands-on exercises at different levels, as well as an online course and a webinar. + +The `ggplot2` package has a unique syntax for building plots, in which you start making a plot by calling the function `ggplot()`, and then add features by layering on other `ggplot2` functions with the `+` symbol. + +If you call `ggplot()`, you'll see that the function immediately draws a blank plot in your Plots pane in RStudio. +```{r} + +ggplot() + +``` + +The plot will still remain blank even when you supply information about your data in order to set up plot aesthetics. +```{r} + +ggplot(data = rfid_combined) + +``` + +You need to add other aesthetics functions to this base layer of the plot in order to see your data. The functions that you use to layer aesthetics over the empty plot will depend on the type of plot that you want to make. For this example, you will make a barcode style plot, in which each timestamp is shown as a thin vertical line. Barcode plots can be useful visualizations when you're working with timestamps, since the most important information is contained in one dimension (time on the x-axis). If you were to summarize the number of timestamps recorded on each day, then you could create a line plot instead. + +You can layer the function `geom_segment()` over the base plot layer. `geom_segment()` allows you to add lines to a plot, and the lines can communicate information in one or two dimensions (width on the x-axis and height on the y-axis). In the plot you'll make below, you'll use `geom_segment()` to add lines to the plot that contain temporal information in one dimension (timestamps that provide information on the x-axis only). +```{r} + +ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + +``` + +In the code above, `geom_segment()` adds a vertical line segment to the plot for each detection in the full dataset. Using the argument `color` inside of `geom_segment()`, you told the function that these line segments should be colored by dataset by supplying the column name that holds the dataset labels. The `color` argument must be inside of the `aes()` function (that controls the aesthetics for this layer of information) in order for this color assignment by dataset to work correctly. + +The line segment colors are automatically assigned by `ggplot` using default colors, but you can change these colors using the function `scale_color_manual()`: +```{r} + +ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + +``` + +The line segments are now colored with the new colors you specified. But in the legend, the pre-processed data shows up first. If you want to change the order of the dataset labels in the legend, and the colors assigned to them, you can modify the `dataset` column in the data frame that you used for plotting. + +`ggplot` functions use a data type called `factors` for automated encoding of aesthetics, such as the color encoding you used above. Columns (or vectors) in factor format can look like `character` type columns, but R stores the unique values of each column as integers, and stores the unique character values in a property called "levels". You can change the order in which values of a column are plotted by changing the order of these levels of a factor column: +```{r} + +# Change the column dataset to data type "factor" +# By specifying "raw" first in the argument levels, you are reordering the factor levels so that "raw" comes first +rfid_combined <- rfid_combined %>% + dplyr::mutate( + dataset = factor(dataset, levels = c("raw", "pre-processed")) + ) + +# The dataset column is now type "fct", or "factor" +glimpse(rfid_combined) + +# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order +levels(rfid_combined$dataset) + +``` + +After converting the `dataset` column to type "factor" amnd reordering the levels of the unique character values in this column, the unique values should appear in the correct order in the plot legend (not in alphabetical order). You should also see that the colors assigned to each dataset changed. +```{r} + +ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + +``` + +In the plot that you just made, it's very difficult to discriminate between the lines for each dataset however. You can use the function `facet_wrap()` to split up the two datasets into different panels: +```{r} + +ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + +``` + +By faceting this plot, you split up the datasets into different panels but also lined them up on the x-axis so that it's easier to compare temporal patterns. + +From this point of view though, it's hard to see how the raw and pre-processed datasets differ. You can filter the data with `tidyverse` functions to plot the first 2 detections for each of the raw and pre-processed datasets: +```{r} + +ggplot(data = rfid_combined %>% + # Group the data frame by each level in the column dataset + group_by(dataset) %>% + # For each unique dataset in the data frame, select the first two rows of data + slice(1:2) %>% + ungroup() + ) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + +``` + +You should be able to see that the second timestamp in the raw data was dropped from the pre-processed dataset (it was removed under the temporal threshold used by `preprocess_detections`). + +Next, you can add the perching data in the data frame `rfid_perch` that you didn't combine with the other two datasets. This dataset doesn't have a single timestamps column, but rather has two columns that indicate the start and the end of each perching event, respectively. You can add this dataset to the full plot by using another `geom_segment()` layer. You'll use `geom_segment()` to add lines to the plot that contain temporal information about when perching events started and ended. +```{r} + +ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + + + # Add the perching events to the plot + geom_segment( + data = rfid_perch, + aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), + color = "blue", + linewidth = 0.3 + ) + +``` + +In the code above for `geom_segment()`, you specified that you wanted to add another dataset to the plot using the argument `data`. The arguments `x` and `y` determine where the beginning of each line segment is drawn on the x and y-axes, respectively. You also need to specify where you want the line segment to end on each axis. On the x-axis, by supplying the column name `perching_start` to the argument `x`, and `perching_end` to the argument `xend`, you're indicating that you want the line segment to start and end at the times when perching was inferred to start and end. On the y-axis, the numbers that you supplied to `y` and `yend` determined where the line segments for perching events were drawn, which was just above the other datasets. Note that the perching events were layered over each facet of the plot by default. + +There are some additional changes you can make to this plot to make it more interpretable. You can change the position of the legend using the argument `legend.position` inside of the general function `theme()`. Below you will also save the plot inside of an object, so that you don't have to write out all of the code over and over. +```{r} + +gg <- ggplot(data = rfid_combined) + + + # Add a vertical line for each timestamp + geom_segment( + aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset), + linewidth = 0.3 + ) + + + scale_color_manual(values = c("orange", "darkgreen")) + + + # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset + facet_wrap(~ dataset, nrow = 2, strip.position = "left") + + + # Add the perching events to the plot + geom_segment( + data = rfid_perch, + aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5), + color = "blue", + linewidth = 0.3 + ) + + + theme( + legend.position = "top" + ) + +gg + +``` + +You can make some minor adjustments to the plot to make it easier on the eyes, including changing the axis titles to be more informative, changing the background to be white, and removing the y-axis text and axis ticks. The y-axis does not contain information for interpretation because the height of each segment does not reflect data that you want to interpret. +```{r} + +gg <- gg + + + # Change the x and y axis title + xlab("Date and time") + + + # The y-axis does not contain information right now, so this title can be blank + ylab("") + + + # Use this function to convert the plot background to black and white + theme_bw() + + + # Use aesthetics functions to remove the y-axis text and ticks + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + +gg + +``` + +You can save plots that you make in R as physical image files. Below you'll use the function `ggsave()` to write the plot to an image file on your computer. +```{r} + +gg + +# Save the image file to your computer +ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. + +In the next vignette, you will continue the ABISSMAL data analysis pipeline and make a more complex and refined barcode style figure. \ No newline at end of file diff --git a/R/vignettes/Vignette_05_ProcessData_BuildPlots.html b/R/vignettes/Vignette_05_ProcessData_BuildPlots.html new file mode 100644 index 0000000..4d0d2fe --- /dev/null +++ b/R/vignettes/Vignette_05_ProcessData_BuildPlots.html @@ -0,0 +1,2282 @@ + + + + + + + + + + + + + + + +Vignette 05: Process and Plot Data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this fifth vignette, you will begin using the simulated detections +of animal movements in the ABISSMAL data processing and analysis +workflow, including combining raw data across days and pre-processing +the raw data. You will also make visualizations of the processed data. +You will continue to use coding skills that you learned in the previous +vignettes, and you will learn additional skills that include:

+
    +
  1. Accessing custom functions
  2. +
  3. Using custom functions
  4. +
  5. Making graphs with ggplot
  6. +
+

+Load packages and your working directory path +

+
rm(list = ls()) # Clean global environment
+
+library(tidyverse) # Load the set of tidyverse packages
+library(data.table) # Load other packages that the ABISSMAL functions require
+
+# Initialize an object with the path that is your working directory
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"
+

+Load ABISSMAL functions +

+

The custom R functions available through ABISSMAL are stored in +physical files (extension .R) inside the local repository on your +computer (which you should have downloaded in vignette 01). In order to +start using the ABISSMAL functions, you need to load the physical .R +files so that the functions are available in your global environment. In +the code below, you will use the function source() to load +3 of the 5 main ABISSMAL functions, plus a script that holds a set of +utility functions:

+
# Load the function that combines raw data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/combine_raw_data.R")
+
+# Load the function that detects perching events in the raw data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_perching_events.R")
+
+# Load the function that pre-processes raw data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/preprocess_detections.R")
+
+# Load a script with utility functions that each function above requires
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")
+

+Access ABISSMAL function information +

+

After running the lines of code above, you should see that a whole +set of functions have been loaded into your global environment (check +the Environment pane). Many of these functions start with +check_, and those are utility functions. If you scroll +down, you’ll see that the three of the main ABISSMAL functions above +(combine_raw_data, detect_perching_events, +preprocess_detections) are all loaded in your global +environment as well. In the column to the right of the function names +you can also see a preview of each function’s arguments.

+

To get more information about each of these three main functions, you +can click the white square icon to the very right of each function in +the Environment pane, or run the code +View(function_name). This will open the script for the +given function a new tab in your Source pane. In each script for each +function, you’ll see lines of documentation starting with the symbols +“`# @”. You’ll see the function name and description first, and then a +description of each argument (parameter) for the function. If you keep +scrolling down, you’ll see a section with details about how the given +function works, and the information that it returns. After the lines of +documentation, you’ll see the code that makes up the function +itself.

+

+Combine raw data +

+

Once you’ve loaded the ABISSMAL functions, you can start using the +first function, combine_raw_data(), to combine data +collected across days per sensor into a single spreadsheet per sensor. +You’ll start by combining raw data for the RFID sensor collected over +different days into a single spreadsheet.

+

Here goes more information about the arguments that you are supplying +to the combine_raw_data() function below:

+
    +
  • sensors is a vector containing the labels of the +sensors for which you want to combine raw data. Below you’re specifying +RFID as a single sensor

  • +
  • path is your general working directory

  • +
  • data_dir is the folder that holds data inside of +your working directory

  • +
  • out_dir is the folder where you want to save the +combined raw data spreadsheet. The function will create this folder if +it does not already exist

  • +
  • tz is the timezone for converting timestamps to +POSIXct format. The default is “America/New York”, and you can check out +the “Time zones” section in the documentation for DateTimeClasses in R +for more information (?DateTimeClasses)

  • +
  • POSIXct_format is a string containing the POSIX +formatting information for how dates and timestamps should be combined +into a single column. The default is to encode the year as a 4 digit +number and the month and day as 2 digit numbers, separated by dashes. +The date is followed by a space, then the 2-digit hour, minute, and +decimal second (separated by colons)

  • +
+
# Combine raw data for the RFID and infrared beam breaker sensors separately
+combine_raw_data(sensors = "RFID", path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

You can check that combine_raw_data() wrote a +spreadsheet of raw RFID data to the new directory +raw_combined:

+
list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")
+
## [1] "combined_raw_data_IRBB.csv" "combined_raw_data_RFID.csv"
+

You can read the combined .csv file (called +“combined_raw_data_RFID.csv”) back into R to check out the structure of +this spreadsheet:

+
rfid_data <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv"))
+
+glimpse(rfid_data)
+
## Rows: 42
+## Columns: 11
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, …
+## $ original_timestamp <chr> "10:00:00", "10:05:00", "11:00:00", "11:05:00", "08…
+## $ timestamp_ms       <chr> "2023-08-01 10:00:00", "2023-08-01 10:05:00", "2023…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ data_stage         <chr> "raw_combined", "raw_combined", "raw_combined", "ra…
+## $ date_combined      <chr> "2024-04-07 2024-04-07 19:16:13.407951", "2024-04-0…
+

Reading this spreadsheet back into R created a data frame object. You +should be able to see that there are some new columns created by the +function, such as the column data_type. For this +spreadsheet, the columns sensor_id and +data_type contain the same information, but it’s useful to +have separate columns to keep track of the sensor id and type of sensor +used to collect data when multiple sensors are used (e.g. two beam +breaker pairs will each have a unique identifier in the +sensor_id column).

+

The function combine_raw_data() also created a new +timestamps column in POSIXct format for downsteam processing and +analysis, but it kept the original timestamps column. The function then +added columns to indicate the stage of data processing and the date that +the raw data were combined. Finally, if you check the folders with the +original raw RFID data, you’ll see that the original spreadsheets per +day were retained and were not overwritten.

+

You can also run combine_raw_data() with raw data from +multiple sensors by supplying a vector with the sensor labels to the +argument sensors. The function will still combine the raw +data per sensor into separate spreadsheets per sensor, and in the +process, you can avoid copy-pasting the code multiple times to run this +process for multiple sensors:

+
combine_raw_data(sensors = c("RFID", "IRBB"), path = path, data_dir = "Data", out_dir = "Data/raw_combined", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

The RFID data will be overwritten, and you should see an additional +spreadsheet with the raw IRBB data:

+
list.files(file.path(path, "Data/raw_combined"), pattern = ".csv$")
+
## [1] "combined_raw_data_IRBB.csv" "combined_raw_data_RFID.csv"
+

+Detect perching events +

+

You can use the combined raw data from different sensors in +subsequent ABISSMAL functions to start making behavioral inferences from +the movement detection datasets. For instance, you can detect perching +events in the raw RFID data with the function +detect_perching_events(). You can read more about each +argument in the R script that contains this function.

+
detect_perching_events(file_nm = "combined_raw_data_RFID.csv", threshold = 2, run_length = 2, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", general_metadata_cols = c("chamber_id", "sensor_id"), path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", out_file_prefix = "perching_events", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

detect_perching_events() can only operate on a single +file and sensor type at a time. The function will automatically create a +new folder called “processed”, and will save the .csv inside of that +folder with perching events (if these were detected using the temporal +threshold and run length above).

+

When we simulated data in the third and fourth vignettes, you +simulated perching events in the RFID dataset only. Did the code above +recover those perching events?

+
perching <- read.csv(file.path(path, "Data", "processed", "perching_events_RFID.csv"))
+
+glimpse(perching)
+
## Rows: 6
+## Columns: 11
+## $ chamber_id              <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "N…
+## $ sensor_id               <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID"
+## $ PIT_tag                 <chr> "1357aabbcc", "2468zzyyxx", "1357aabbcc", "246…
+## $ perching_start          <chr> "2023-08-01 08:00:00", "2023-08-01 11:30:00", …
+## $ perching_end            <chr> "2023-08-01 08:00:03", "2023-08-01 11:30:05", …
+## $ perching_duration_s     <int> 3, 5, 3, 5, 3, 5
+## $ unique_perching_event   <int> 1, 2, 3, 4, 5, 6
+## $ min_perching_run_length <int> 2, 2, 2, 2, 2, 2
+## $ threshold_s             <int> 2, 2, 2, 2, 2, 2
+## $ data_stage              <chr> "pre-processing", "pre-processing", "pre-proce…
+## $ date_preprocessed       <chr> "2024-04-07 2024-04-07 19:16:13.959793", "2024…
+

detect_perching_events() identified 6 total perching +events, which is the same number that you simulated in the previous +vignette (two perching events per day). You can look at the values +inside of data frame for more information about these perching +events:

+
# The timestamps when each perching event started
+perching$perching_start
+
## [1] "2023-08-01 08:00:00" "2023-08-01 11:30:00" "2023-08-02 08:00:00"
+## [4] "2023-08-02 11:30:00" "2023-08-03 08:00:00" "2023-08-03 11:30:00"
+
# The timestamps when each perching event ended
+perching$perching_end
+
## [1] "2023-08-01 08:00:03" "2023-08-01 11:30:05" "2023-08-02 08:00:03"
+## [4] "2023-08-02 11:30:05" "2023-08-03 08:00:03" "2023-08-03 11:30:05"
+
# The unique PIT tag identifier that tells you which individual was perched on the RFID antenna
+perching$PIT_tag
+
## [1] "1357aabbcc" "2468zzyyxx" "1357aabbcc" "2468zzyyxx" "1357aabbcc"
+## [6] "2468zzyyxx"
+

You can also view the whole data frame in a separate pane:

+
View(perching)
+

The information above tells you that there were 2 perching events +detected at 8:00 each day, and 2 perching events detected at 11:30 each +day (as expected). The PIT tag for each individual was detected once +each day, so there was 1 perching event performed by each individual on +each day.

+

Detecting perching events is not a requirement in the ABISSMAL +workflow, but it can be a useful step to obtain as much information from +the raw data before some detections are dropped during pre-processing +(see below).

+

+Pre-process raw data +

+

Once you’ve detected perching events in the raw data, you can move on +to pre-processing the raw data itself with +preprocess_detections(). The raw data can sometimes contain +multiple detections separated by a short period of time (e.g. RFID +detections when an individual is perching on the antenna), and these +multiple detections can be a source of noise when you’re trying to make +behavioral inferences across data collected by multiple sensors. When +the argument mode is set to “thin”, +preprocess_detections() removes detections separated by a +very short period of time (determined by the temporal threshold in +seconds passed to the argument thin_threshold), and retains +a reduced datasets of detections that still represents discrete movement +events.

+

preprocess_detections() also operates on a single sensor +type at a time:

+
preprocess_detections(sensor = "RFID", timestamps_col_nm = "timestamp_ms", group_col_nm = "PIT_tag", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

You should now see an additional .csv file called +“pre_processed_data_RFID.csv” in the “processed” folder:

+
list.files(file.path(path, "Data/processed"))
+
## [1] "detection_clusters.csv"       "perching_events_RFID.csv"    
+## [3] "pre_processed_data_IRBB.csv"  "pre_processed_data_RFID.csv" 
+## [5] "scored_detectionClusters.csv"
+

You can read this file into R to check out the data structure. You +should see that there are fewer rows here compared to the spreadsheet of +raw data:

+
rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv"))
+
+glimpse(rfid_pp)
+
## Rows: 27
+## Columns: 11
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ timestamp_ms       <chr> "2023-08-01 08:00:00", "2023-08-01 08:00:02", "2023…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:16:14.288463", "2024-04-0…
+

Next, you can pre-process the raw beam breaker data and check out the +resulting .csv file:

+
preprocess_detections(sensor = "IRBB", timestamps_col_nm = "timestamp_ms", group_col_nm = "sensor_id", mode = "thin", thin_threshold = 2, drop_tag = NULL, path = file.path(path, "Data"), data_dir = "raw_combined", out_dir = "processed", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+
+list.files(file.path(path, "Data/processed"))
+
## [1] "detection_clusters.csv"       "perching_events_RFID.csv"    
+## [3] "pre_processed_data_IRBB.csv"  "pre_processed_data_RFID.csv" 
+## [5] "scored_detectionClusters.csv"
+
irbb_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_IRBB.csv"))
+
+glimpse(irbb_pp)
+
## Rows: 66
+## Columns: 10
+## $ data_type          <chr> "IRBB", "IRBB", "IRBB", "IRBB", "IRBB", "IRBB", "IR…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms       <chr> "2023-08-01 06:05:04", "2023-08-01 06:05:05", "2023…
+## $ sensor_id          <chr> "Inner Beam Breakers", "Outer Beam Breakers", "Oute…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:16:14.399455", "2024-04-0…
+

+Plot RFID data in a barcode plot +

+

Now that you’ve combined and processed the raw data per sensor, it’s +time to visualize these different datasets. Making visualizations as you +write code is important for generating high-quality figures for +publications and presentations, but also for checking your work.

+

In the code below, you’ll learn how to use functions from the +ggplot2 package to make a barcode style figure of the raw +and pre-processed RFID detection datasets.

+

Start by reading in the raw and pre-processed RFID data, as well as +the RFID perching events, and convert the timestamps to POSIX +format.

+
rfid_raw <- read.csv(file.path(path, "Data/raw_combined", "combined_raw_data_RFID.csv")) %>%
+  # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>%
+  # Order the data frame by timestamps. The -desc() inside of arrange() means that the timestamps will be arranged in increasing order (less to more recent)
+  dplyr::arrange(-desc(timestamp_ms))
+
+# You should see that the timestamp_ms is in "dttm" format, which means that the POSIX conversion was done correctly
+glimpse(rfid_raw)
+
## Rows: 42
+## Columns: 11
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, …
+## $ original_timestamp <chr> "08:00:00", "08:00:01", "08:00:02", "08:00:03", "10…
+## $ timestamp_ms       <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:01, 2023-08-…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ data_stage         <chr> "raw_combined", "raw_combined", "raw_combined", "ra…
+## $ date_combined      <chr> "2024-04-07 2024-04-07 19:16:13.576772", "2024-04-0…
+
rfid_pp <- read.csv(file.path(path, "Data/processed/pre_processed_data_RFID.csv")) %>%
+  # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    timestamp_ms = as.POSIXct(format(as.POSIXct(timestamp_ms, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>% 
+  dplyr::arrange(-desc(timestamp_ms))
+
+glimpse(rfid_pp)
+
## Rows: 27
+## Columns: 11
+## $ data_type          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ chamber_id         <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "Nest_0…
+## $ year               <int> 2023, 2023, 2023, 2023, 2023, 2023, 2023, 2023, 202…
+## $ month              <int> 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, …
+## $ day                <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ timestamp_ms       <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:02, 2023-08-…
+## $ PIT_tag            <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "1357aabb…
+## $ sensor_id          <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RF…
+## $ thin_threshold_s   <int> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, …
+## $ data_stage         <chr> "pre-processed", "pre-processed", "pre-processed", …
+## $ date_pre_processed <chr> "2024-04-07 2024-04-07 19:16:14.288463", "2024-04-0…
+
rfid_perch <- read.csv(file.path(path, "Data/processed/perching_events_RFID.csv")) %>%
+  # The start and end timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    perching_start = as.POSIXct(format(as.POSIXct(perching_start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")),
+    perching_end = as.POSIXct(format(as.POSIXct(perching_end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>% 
+  dplyr::arrange(-desc(perching_start))
+
+glimpse(rfid_perch)
+
## Rows: 6
+## Columns: 11
+## $ chamber_id              <chr> "Nest_01", "Nest_01", "Nest_01", "Nest_01", "N…
+## $ sensor_id               <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID"
+## $ PIT_tag                 <chr> "1357aabbcc", "2468zzyyxx", "1357aabbcc", "246…
+## $ perching_start          <dttm> 2023-08-01 08:00:00, 2023-08-01 11:30:00, 2023…
+## $ perching_end            <dttm> 2023-08-01 08:00:03, 2023-08-01 11:30:05, 202…
+## $ perching_duration_s     <int> 3, 5, 3, 5, 3, 5
+## $ unique_perching_event   <int> 1, 2, 3, 4, 5, 6
+## $ min_perching_run_length <int> 2, 2, 2, 2, 2, 2
+## $ threshold_s             <int> 2, 2, 2, 2, 2, 2
+## $ data_stage              <chr> "pre-processing", "pre-processing", "pre-proc…
+## $ date_preprocessed       <chr> "2024-04-07 2024-04-07 19:16:13.959793", "2024…
+

Next, you can combine the original and pre-processed datasets into a +single data frame to facilitate combining them in the same plot. You +will add a new column (dataset) with labels in order to +identify the two different datasets.

+
rfid_combined <- rfid_raw %>%
+  dplyr::select(sensor_id, day, timestamp_ms) %>% 
+  dplyr::mutate(
+    dataset = "raw"
+  ) %>% 
+  bind_rows(
+    rfid_pp %>%
+      dplyr::select(sensor_id, day, timestamp_ms) %>% 
+      dplyr::mutate(
+        dataset = "pre-processed"
+      ) 
+  ) %>% 
+  dplyr::arrange(-desc(timestamp_ms))
+
+glimpse(rfid_combined)
+
## Rows: 69
+## Columns: 4
+## $ sensor_id    <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "…
+## $ day          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:00, 2023-08-01 08:…
+## $ dataset      <chr> "raw", "pre-processed", "raw", "raw", "pre-processed", "r…
+

You will start plotting the data using functions from +ggplot2. This package is part of the +tidyverse, but can also be installed and used separately. +You can check out this link +for more resources to learn how to use ggplot notation to make different +types of plots. These resources include sections of three different +books with hands-on exercises at different levels, as well as an online +course and a webinar.

+

The ggplot2 package has a unique syntax for building +plots, in which you start making a plot by calling the function +ggplot(), and then add features by layering on other +ggplot2 functions with the + symbol.

+

If you call ggplot(), you’ll see that the function +immediately draws a blank plot in your Plots pane in RStudio.

+
ggplot()
+

+

The plot will still remain blank even when you supply information +about your data in order to set up plot aesthetics.

+
ggplot(data = rfid_combined)
+

+

You need to add other aesthetics functions to this base layer of the +plot in order to see your data. The functions that you use to layer +aesthetics over the empty plot will depend on the type of plot that you +want to make. For this example, you will make a barcode style plot, in +which each timestamp is shown as a thin vertical line. Barcode plots can +be useful visualizations when you’re working with timestamps, since the +most important information is contained in one dimension (time on the +x-axis). If you were to summarize the number of timestamps recorded on +each day, then you could create a line plot instead.

+

You can layer the function geom_segment() over the base +plot layer. geom_segment() allows you to add lines to a +plot, and the lines can communicate information in one or two dimensions +(width on the x-axis and height on the y-axis). In the plot you’ll make +below, you’ll use geom_segment() to add lines to the plot +that contain temporal information in one dimension (timestamps that +provide information on the x-axis only).

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  )
+

+

In the code above, geom_segment() adds a vertical line +segment to the plot for each detection in the full dataset. Using the +argument color inside of geom_segment(), you +told the function that these line segments should be colored by dataset +by supplying the column name that holds the dataset labels. The +color argument must be inside of the aes() +function (that controls the aesthetics for this layer of information) in +order for this color assignment by dataset to work correctly.

+

The line segment colors are automatically assigned by +ggplot using default colors, but you can change these +colors using the function scale_color_manual():

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen"))
+

+

The line segments are now colored with the new colors you specified. +But in the legend, the pre-processed data shows up first. If you want to +change the order of the dataset labels in the legend, and the colors +assigned to them, you can modify the dataset column in the +data frame that you used for plotting.

+

ggplot functions use a data type called +factors for automated encoding of aesthetics, such as the +color encoding you used above. Columns (or vectors) in factor format can +look like character type columns, but R stores the unique +values of each column as integers, and stores the unique character +values in a property called “levels”. You can change the order in which +values of a column are plotted by changing the order of these levels of +a factor column:

+
# Change the column dataset to data type "factor"
+# By specifying "raw" first in the argument levels, you are reordering the factor levels so that "raw" comes first 
+rfid_combined <- rfid_combined %>% 
+  dplyr::mutate(
+    dataset = factor(dataset, levels = c("raw", "pre-processed"))
+  )
+
+# The dataset column is now type "fct", or "factor"
+glimpse(rfid_combined)
+
## Rows: 69
+## Columns: 4
+## $ sensor_id    <chr> "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "RFID", "…
+## $ day          <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
+## $ timestamp_ms <dttm> 2023-08-01 08:00:00, 2023-08-01 08:00:00, 2023-08-01 08:…
+## $ dataset      <fct> raw, pre-processed, raw, raw, pre-processed, raw, raw, pr…
+
# The levels of the factor column are ordered with "raw" first, rather than in alphabetical order
+levels(rfid_combined$dataset)
+
## [1] "raw"           "pre-processed"
+

After converting the dataset column to type “factor” +amnd reordering the levels of the unique character values in this +column, the unique values should appear in the correct order in the plot +legend (not in alphabetical order). You should also see that the colors +assigned to each dataset changed.

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen"))
+

+

In the plot that you just made, it’s very difficult to discriminate +between the lines for each dataset however. You can use the function +facet_wrap() to split up the two datasets into different +panels:

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left")
+

+

By faceting this plot, you split up the datasets into different +panels but also lined them up on the x-axis so that it’s easier to +compare temporal patterns.

+

From this point of view though, it’s hard to see how the raw and +pre-processed datasets differ. You can filter the data with +tidyverse functions to plot the first 2 detections for each +of the raw and pre-processed datasets:

+
ggplot(data = rfid_combined %>%
+         # Group the data frame by each level in the column dataset
+         group_by(dataset) %>%
+         # For each unique dataset in the data frame, select the first two rows of data
+         slice(1:2) %>% 
+         ungroup()
+       ) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left")
+

+

You should be able to see that the second timestamp in the raw data +was dropped from the pre-processed dataset (it was removed under the +temporal threshold used by preprocess_detections).

+

Next, you can add the perching data in the data frame +rfid_perch that you didn’t combine with the other two +datasets. This dataset doesn’t have a single timestamps column, but +rather has two columns that indicate the start and the end of each +perching event, respectively. You can add this dataset to the full plot +by using another geom_segment() layer. You’ll use +geom_segment() to add lines to the plot that contain +temporal information about when perching events started and ended.

+
ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left") +
+  
+  # Add the perching events to the plot
+  geom_segment(
+    data = rfid_perch,
+    aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5),
+    color = "blue",
+    linewidth = 0.3
+  )
+

+

In the code above for geom_segment(), you specified that +you wanted to add another dataset to the plot using the argument +data. The arguments x and y +determine where the beginning of each line segment is drawn on the x and +y-axes, respectively. You also need to specify where you want the line +segment to end on each axis. On the x-axis, by supplying the column name +perching_start to the argument x, and +perching_end to the argument xend, you’re +indicating that you want the line segment to start and end at the times +when perching was inferred to start and end. On the y-axis, the numbers +that you supplied to y and yend determined +where the line segments for perching events were drawn, which was just +above the other datasets. Note that the perching events were layered +over each facet of the plot by default.

+

There are some additional changes you can make to this plot to make +it more interpretable. You can change the position of the legend using +the argument legend.position inside of the general function +theme(). Below you will also save the plot inside of an +object, so that you don’t have to write out all of the code over and +over.

+
gg <- ggplot(data = rfid_combined) +
+  
+  # Add a vertical line for each timestamp
+  geom_segment(
+    aes(x = timestamp_ms, y = 0, xend = timestamp_ms, yend = 1, color = dataset),
+    linewidth = 0.3
+  ) +
+  
+  scale_color_manual(values = c("orange", "darkgreen")) +
+  
+  # ~ is equivalent to "by", so that you're creating panels by the unique values in the column dataset
+  facet_wrap(~ dataset, nrow = 2, strip.position = "left") +
+  
+  # Add the perching events to the plot
+  geom_segment(
+    data = rfid_perch,
+    aes(x = perching_start, xend = perching_end, y = 1.2, yend = 1.5),
+    color = "blue",
+    linewidth = 0.3
+  ) +
+  
+  theme(
+    legend.position = "top"
+  )
+
+gg
+

+

You can make some minor adjustments to the plot to make it easier on +the eyes, including changing the axis titles to be more informative, +changing the background to be white, and removing the y-axis text and +axis ticks. The y-axis does not contain information for interpretation +because the height of each segment does not reflect data that you want +to interpret.

+
gg <- gg +
+  
+  # Change the x and y axis title
+  xlab("Date and time") +
+  
+  # The y-axis does not contain information right now, so this title can be blank
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  )
+
+gg
+

+

You can save plots that you make in R as physical image files. Below +you’ll use the function ggsave() to write the plot to an +image file on your computer.

+
gg
+

+
# Save the image file to your computer
+ggsave(file.path(path, "raw_processed_perching.tiff"), width = 8, height = 6, units = "in", dpi = 300)
+

You can continue to modify minor aesthetics to this image file to +create a high-quality figure for a publication. For instance, you can +change the final size of the image file (width, +height), as well as the resolution (dpi). You +can also change the size of the text on each axis and the axis titles, +or the legend position, as you play around with the final image +size.

+

In the next vignette, you will continue the ABISSMAL data analysis +pipeline and make a more complex and refined barcode style figure.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd new file mode 100644 index 0000000..7c294ad --- /dev/null +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.Rmd @@ -0,0 +1,560 @@ +--- +title: "Vignette 06: Finish Analysis Pipeline" +author: "Grace Smith-Vidaurre" +date: "2023-12-27" +output: + html_document: + css: "styles.css" + toc: true + toc_float: true + toc_depth: 4 +--- + +```{r setup, include = FALSE} + +knitr::opts_chunk$set(echo = TRUE, eval = TRUE) + +``` + +

Vignette overview and learning objectives

+ +In this sixth and last vignette, you will finish using the simulated detections of animal movements in the ABISSMAL data processing and analysis workflow. You will detect clusters of detections that represent distinct movement events, and then score behavioral inferences about the movement events. You will also make plots to visualize these behavioral inferences. You will continue to use coding skills that you learned in the previous vignettes, and you will additionally learn how to build more complex visualizations with ggplot. + +

Load packages and your working directory path

+ +```{r message = FALSE, warning = FALSE} + +rm(list = ls()) # Clean global environment + +library(tidyverse) # Load the set of tidyverse packages +library(data.table) # Load other packages that the ABISSMAL functions require + +# Initialize an object with the path that is your working directory +path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes" + +``` + +

Load ABISSMAL functions

+ +```{r} + +# Load the function that detects clusters in the pre-processed data +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R") + +# Load the function that scores behavioral inferences about clusters +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R") + +# Load a script with utility functions that each function above requires +source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R") + +``` + +

Finish the ABISSMAL pipeline

+ +Here you'll use the ABISSMAL function `detect_clusters()` to identify clusters of detections across sensor types (e.g. detections from different sensors that were recorded close together in time). +```{r} + +# The run length needs to be set to 1 in order to correctly detect detection clusters of length 2 +detect_clusters(file_nms = c("pre_processed_data_RFID.csv", "pre_processed_data_IRBB.csv"), threshold = 2, run_length = 1, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", preproc_metadata_col_nms = c("thin_threshold_s", "data_stage", "date_pre_processed"), general_metadata_col_nms = c("chamber_id", "year", "month", "day"), path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "detection_clusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +Next, you'll score behavioral inferences about these detection clusters with the function `score_clusters()`. +```{r} + +score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_label = NULL, outer_irbb_label = "Outer Beam Breakers", inner_irbb_label = "Inner Beam Breakers", video_metadata_col_nms = NULL, integrate_perching = TRUE, perching_dataset = "RFID", perching_prefix = "perching_events_", sensor_id_col_nm = "sensor_id", PIT_tag_col_nm = "PIT_tag", pixel_col_nm = NULL, video_width = NULL, video_height = NULL, integrate_preproc_video = FALSE, video_file_nm = NULL, timestamps_col_nm = NULL, path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "scored_detectionClusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS") + +``` + +

Check final results

+ +Now that you finished running the ABISSMAL pipeline to detect and make inferences about movement events, you can check the final results. +```{r} + +scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% + # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting + dplyr::mutate( + start = as.POSIXct(format(as.POSIXct(start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")), + end = as.POSIXct(format(as.POSIXct(end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")) + ) %>% + # Arrange the rows by increasing timestamps + dplyr::arrange(-desc(start)) + +glimpse(scored_clusters) + +``` + +How many entrances and exits were scored per day? + +

Missing data in R

+ +In order to count the number of each of these events scored per day, you need to know how to account for missing data in R. Missing data is often indicated using the value `NA` or "not available", which is a specific type of logical value in R. You can figure out whether a vector (or a column in a data frame) contains missing values by using the function `is.na()`, which will return TRUE when it finds an NA (missing) value in the given vector. +```{r} + +?is.na() + +x <- c(1, NA, 2, 3, NA) + +is.na(x) + +``` + +In the code above, you created a vector called `x` that has 2 NA values. The function `is.na()` checks whether each element of `x` is equivalent to `NA`, and returns `TRUE` when that condition is met (when it finds missing data). + +Because `is.na()` is a conditional statement, you can also use other special symbols relevant to conditional statements, such as the `!` symbol, which will invert a conditional statement. For instance, in the code below, by adding `!` before `is.na()`, you're now asking whether each element of `x` is *not* equivalent to NA: +```{r} + +!is.na(x) + +``` + +As you can see, adding the `!` in front of `is.na()` results in an inverted output compared to `is.na()` alone, such that each value that was previously `TRUE` is now `FALSE`. Together, the ability to invert the `is.na()` conditional statement, plus the binary output that `is.na()` returns, is really useful for finding and filtering rows of a data frame. + +For instance, the function `dplyr::filter()` will drop a row whenever it encounters a value of `FALSE` in a given column, and will retain a row whenever it encounters a value of `TRUE`. If you want to drop rows that contain `NA` values for a given column, you would add `!is.na(name_of_column)` inside of `dplyr::filter()`, which should return `FALSE` every time it encounters a row with `NA`, and will remove that row during filtering. + +

Count events per day

+ +Now you can write code to count the number of entrance and exit events scored per day. You will need to 1) make a new column with information about the day per timestamp, 2) drop rows with missing data for the direction scored (because this information cannot be scored for some movements), 3) group the data frame by day and direction scored, and then 4) count the number of rows per group. +```{r} + +scored_clusters %>% + # Extract the day from each timestamp and make a new column with this information + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Here you're using the function is.na(), which will return TRUE when it finds an NA (missing) value in the given column. By placing the ! before is.na(), you're inverting the output, so that all TRUE values are converted to FALSE. As a result, dplyr::filter will drop all rows that return the value FALSE (e.g. all rows with missing values in the column direction_scored) + dplyr::filter(!is.na(direction_scored)) %>% + # Group the data frame by both columns for which you want to count rows (events). Here you want to count the number of entrances and exits (categories in the column direction_scored) per day (categories in the column day) + group_by(day, direction_scored) %>% + # Then summarize the data: the number of rows here is the number of exits or entrances scored per day + dplyr::summarise( + n = n() + ) + +``` + +Four entrance and exit events were scored per day. Does this line up with the anticipated number of exits and entrances per day? If you go back to the code where you created the simulated raw datasets in vignettes 03 and 04, you should see that you started by simulating 2 entrances and exits per day across the RFID and beam breaker datasets. Then, you added 2 more entrance and 2 more exit events per day when you simulated RFID detection failures (e.g. movements captured by the beam breakers only). `score_clusters()` detected the correct number of entrances and exits per day. + +Next, check out the perching events. Start by filtering out all rows that were not scored as perching events (rows that have NA values in the column "perching_PIT_tag"). Then, select only the columns that have information that is useful to check: the PIT tag codes, as well as the start and end of each perching event. +```{r} + +scored_clusters %>% + # Use a conditional statement with is.na() to retain only rows that have PIT tag codes associated with perching events + dplyr::filter(!is.na(perching_PIT_tag)) %>% + # Then select only the columns that you want to check visually + dplyr::select(start, end, perching_PIT_tag) + +``` + +As specified in vignettes 03 and 04, the first perching event each day was performed by the first individual (PIT tag "1357aabbcc"), and the second was attributed to the second individual (PIT tag "2468zzyyxx"). + +How many movement events that were not perching events were attributed to each individual? +```{r} + +scored_clusters %>% + # Extract the day from each timestamp and make a new column with this information + dplyr::mutate( + day = lubridate::day(start) + ) %>% + # Use a conditional statement with is.na() to retain only rows that have PIT tag codes not associated with perching events + # Here you're combining two conditional statements, which allows you to search for rows in the column individual_initiated that have PIT tag codes, but that also do not have a sensor label in the column perching_sensor (e.g. movement events that were not perching events) + dplyr::filter(!is.na(individual_initiated) & is.na(perching_sensor)) %>% + group_by(day, individual_initiated) %>% + dplyr::summarise( + n = n() + ) + +``` + +As you can see, 4 movement events that were not perching events were attributed to the first individual (PIT tag code 1357aabbcc) on each day. This is exactly what we expected when creating the simulated dataset (see vignettes 03 and 04). More non-perching movement events were detected across these days, but those events were not always captured by the RFID antenna (e.g. the simulated RFID detection failures that were captured by the beam breakers only). The beam breakers captured those movement events but cannot record individual identity. + +

Build complex barcode plot

+ +Now that you checked out the final results, you can visualize them. In the code below, you'll learn how to build a barcode plot that is more complex but also easier to interpret than the plot that you made in vignette 05. For this plot, it would be helpful to visualize 3 types of behavioral inferences or types of information over time: the direction of movement (when available), the identity of the individual (when available), and perching events. + +You can start building the plot by adding vertical line segments for non-perching movement events. The color of each line will indicate the individual identity, or whether individual identity could not be assigned. The line type of each line will indicate the direction of movement, or if direction could not be scored. + +In order to encode colors and line types correctly in the plot, you need to modify the data frame of final results to convert NAs in the two associated columns to more meaningful information. For instance, when individual identity is missing, it would be useful to convert the associated NA value to "unassigned". In the code below, you will use the function `is.na()` inside of `ifelse()` conditional statements to change the NA values inside the columns "individual_initiated" and "direction_scored" to more useful information for plotting purposes. + +First you can practice using `is.na()` inside of `ifelse()` to create a new vector. In the code below, you're providing the conditional statement that you want to test (here you're testing whether the column holding the PIT tag code of the individual that initiated a movement has NAs), the value you want to add to the vector if the condition is true ("unassigned" when no PIT tag code is present), and then the value that you want to add if the condition is false (return the PIT tag code in the individual_initiated column if the given value is not NA). +```{r} + +# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA +is.na(scored_clusters$individual_initiated) + +# All NAs in this column are converted to "unassigned", but all other values were not changed +ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated) + +``` + +Now you can use `is.na()` inside of `ifelse` statements to modify columns in the data frame. +```{r} + +scored_clusters_gg <- scored_clusters %>% + dplyr::mutate( + # If this column has an NA value, then convert it to "unassigned" + # Else, do not change the given value + individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated), + # Repeat this process for the direction_scored column but with a different value + # Also, in the conditional statement below, you added is.na(perching_sensor) (after the & symbol) to only convert NA values in direction_scored when they were also not labeled as perching events in the column perching_sensor + direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored) + ) %>% + # Next, you'll convert each of these columns to type factor and arrange the levels in order for plotting (for example, the valures "unassigned" and "not scored" should come last in the legend) + dplyr::mutate( + individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")), + direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored")) + ) + +# Check the resulting changes using the function distinct() to see all of the unique values for both columns that were modified +# The NA values in the direction_scored column are expected since these refer to perching events +scored_clusters_gg %>% + distinct(individual_initiated, direction_scored) + +``` + +This output looks good. You should see `NA` values in this data frame, but they're in the column "direction_scored" and associated with the eprching events, which you'll be adding to the plot in a separate layer of code later on. + +You can use this modified data frame to build the plot. In the code below, you'll add lines colored by individual identity and with line types encoding the direction of movement. First you'll specify the plot aesthetics: +```{r} + +# Colors will be encoded in the same order as the levels of the column individual_initiated, so orange will encode "1357aabbcc" +levels(scored_clusters_gg$individual_initiated) +cols <- c("orange", "darkgreen", "black") + +# Line types will be encoded in the same order as the levels of the column direction_scored, so dotted will encode "not scored" +levels(scored_clusters_gg$direction_scored) +ltys <- c("solid", "longdash", "dotted") + +``` + +Then you can add lines to the plot by individual identity. Here, you're splitting up the `geom_segment()` calls by unique values in the individual_initiated column. You only added segments for the first individual and all non-perching events that were not assigned to an individual, because from checking the results above, you know that no non-perching events were assigned to the second individual. +```{r} + +ggplot() + + + # Add a vertical line for each non-perching event assigned to the first individual + geom_segment( + data = scored_clusters_gg %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Add a vertical line for each non-perching event that was not assigned to either individual + geom_segment( + data = scored_clusters_gg %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + + + # Remove the y-axis title + ylab("") + + + # Use this function to convert the plot background to black and white + theme_bw() + + + # Use aesthetics functions to remove the y-axis text and ticks + # Add an argument to change where the legend is located in the plot + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + +``` + +To make this plot, you vertically offset the line segments for the first individual and those that were unassigned. This vertical offset makes it easier to visually compare patterns over time. You created this vertical offset by changing the values used for `y` and `yend` in the second `geom_segment()` layer, so that those line segments would start higher than the top of the first set of segments. + +You can make some additional modifications that would help make this plot easier to interpret. First, the panel or facet labels on the left could be changed from the date to the day of data collection (e.g. "Day 1"). The x-axis labels could also be changed to show only the time of day (remove the month and day), and there could also be more labels available (e.g. a label each half hour). + +You can start by modifying the data frame to add information about the day of data collection. You'll create this new column by relying on conditional statements with the `ifelse()` function, since there are only 3 days of data collection that need recoding. +```{r} + +# Create a new column in the raw data for the date of data collection +scored_clusters_gg2 <- scored_clusters_gg %>% + # First you need to create a column with information about the day + dplyr::mutate( + day = lubridate::day(start) + ) %>% + dplyr::mutate( + # Then recode the label for each day and save this in a new column + day_label = ifelse(day == 1, "Day 1", day), # Here the last argument is day because the column day_label does not exist yet + day_label = ifelse(day == 2, "Day 2", day_label), + day_label = ifelse(day == 3, "Day 3", day_label) + ) + +# Looks good +glimpse(scored_clusters_gg2) + +scored_clusters_gg2 %>% + distinct(day_label) + +``` + +Now you can update the code to include the new date labels: +```{r} + +# Add the data frame as the default dataset for the base layer plot, so that the facet_wrap() layer below has data to plot +ggplot(data = scored_clusters_gg2) + + + # Add a vertical line for each non-perching event assigned to the first individual + geom_segment( + data = scored_clusters_gg2 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Add a vertical line for each non-perching event that was not assigned to either individual + geom_segment( + data = scored_clusters_gg2 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + + + # Remove the y-axis title + ylab("") + + + # Use this function to convert the plot background to black and white + theme_bw() + + + # Use aesthetics functions to remove the y-axis text and ticks + # Add an argument to change where the legend is located in the plot + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + +``` + +Now that information about the day of data collection has been moved to the facet labels, you need to fix the x-axis labels. The plot will be more interpretable if you can line up the timestamps by hour and minute for a direct comparison across days. + +To line up the timestamps across days, you will need to update the format of the columns that hold timestamps. The code to convert the timestamps to a different format is nested and repetitive, but the timestamp conversion will be performed correctly. When you prompt the R to convert the timestamps to hours, minutes, and seconds only, R adds a default year, month, and day beforehand (likely the current date that you rant the code). This is expected, and it is not an error, but rather makes it possible for the timestamps to align correctly over days in the plot (since R sees all timestamps occurring on a single day). +```{r} + +scored_clusters_gg3 <- scored_clusters_gg2 %>% + dplyr::mutate( + start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")), + end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S")) + ) + +# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above) +glimpse(scored_clusters_gg3) + +``` + +Now you can update the plotting code to change the aesthetics of the x-axis using the function `scale_x_datetime()` to specify that you want x-axis labels every half hour. Also add an x-axis title, and remove the y-axis grid lines inside of each panel: +```{r} + +gg <- ggplot(data = scored_clusters_gg3) + + + # Add a vertical line for each non-perching event assigned to the first individual + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Add a vertical line for each non-perching event that was not assigned to either individual + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + + + # Remove the y-axis title + ylab("") + + + # Use this function to convert the plot background to black and white + theme_bw() + + + # Use aesthetics functions to remove the y-axis text and ticks + # Add an argument to change where the legend is located in the plot + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top" + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + + + # Change the aesthetics of the x-axis text + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # Add an x-axis title + xlab("Time of day (HH:MM)") + + + # Remove the y-axis grid lines (major and minor) inside of each panel + theme( + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank() + ) + +gg + +``` + +There's still one more important piece of information to add to the plot: the perching events. You'll add these using `geom_segment()`, but you'll make these line segments short and thick, so that they appear more like dots on the plot: +```{r} + +gg <- gg + + + # Add the perching events as rounded segments, and now encode color through the column individual_initiated + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(!is.na(perching_sensor)), + aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), + linewidth = 2, lineend = "round" + ) + + + # Add the custom colors. These colors also apply to the lines that represent non-perching events that you added in previous geom_segement() layers + scale_color_manual(values = cols) + +gg + +``` + +You should see that a color legend appears at the top of the plot with the addition of the new `geom_segment()` layer. Both legends are a little messy. You can improve the plot by updating the title of each legend, increasing the size of the legend text, and reducing the white space between the plot and the legends. To modify the legend titles, you'll rely on the functions `guides()` and `guide_legend()`. To increase the legend text size, you'll use the argument `legend.text` inside of the function `theme()`. To reduce white space between the plot and legends, you'll use the argument `legend.margin` inside of the function `theme()`. +```{r} + +# Check out more information about the function used to control margins (white space) around the legend +?margin + +gg <- gg + + + # Increase the legend text size and reduce white space between the plot and legends + theme( + legend.text = element_text(size = 10), + legend.margin = margin(-1, -1, -1, -1, unit = "pt") + ) + + + # Change the legend titles + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") + ) + +gg + +``` + +Finally, you can save this plot as an image file: +```{r} + +gg + +# Save the image file to your computer +ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300) + +``` + +You can continue to modify minor aesthetics to this image file to create a high-quality figure for a publication. For instance, you can change the final size of the image file (`width`, `height`), as well as the resolution (`dpi`). You can also change the size of the text on each axis and the axis titles, or the legend position, as you play around with the final image size. + +This is the all of the code used for the final plot, condensed and reorganized: +```{r eval = FALSE} + +ggplot(data = scored_clusters_gg3) + + + # Add a vertical line for each non-perching event assigned to the first individual + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "1357aabbcc"), + aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored), + color = "orange", + linewidth = 0.5 + ) + + + # Add a vertical line for each non-perching event that was not assigned to either individual + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(individual_initiated == "unassigned"), + aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored), + color = "black", + linewidth = 0.5 + ) + + + # Add the perching events as rounded segments, and now encode color through the column individual_initiated + geom_segment( + data = scored_clusters_gg3 %>% + dplyr::filter(!is.na(perching_sensor)), + aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated), + linewidth = 2, lineend = "round" + ) + + + # Add the custom linetype values to this plot + scale_linetype_manual(values = ltys) + + + # Add the custom colors + scale_color_manual(values = cols) + + + # Change the legend titles + guides( + linetype = guide_legend(title = "Direction"), + color = guide_legend(title = "Individual") + ) + + + # Add an x-axis title + xlab("Time of day (HH:MM)") + + + # Remove the y-axis title + ylab("") + + + # Change the aesthetics of the x-axis text + scale_x_datetime( + date_breaks = "30 mins", + date_labels = "%H:%M" + ) + + + # Facet the plot by day (e.g. create a panel per day) + # Use the new day labels here + facet_wrap(~ day_label, nrow = 3, strip.position = "left") + + + # Use this function to convert the plot background to black and white + theme_bw() + + + # Use aesthetics functions to remove the y-axis text and ticks + # Add an argument to change where the legend is located in the plot + # Remove the y-axis grid lines (major and minor) inside of each panel + # Increase the legend text size and reduce white space between the plot and legend + theme( + axis.text.y = element_blank(), + axis.ticks.y = element_blank(), + legend.position = "top", + panel.grid.major.y = element_blank(), + panel.grid.minor.y = element_blank(), + legend.text = element_text(size = 10), + legend.margin = margin(-1, -1, -1, -1, unit = "pt") + ) + +``` + +You completed the final vignette of the ABISSMAL data processing and analysis pipeline. You also practiced your coding and data science skills in a biological context. Well done! It would be very helpful if you could complete the brief Google form for a post-vignette evaluation that will help us continue to improve these vignettes over time. A link to this Google form will be available in the README file for the vignettes. \ No newline at end of file diff --git a/R/vignettes/Vignette_06_FinishAnalysisPipeline.html b/R/vignettes/Vignette_06_FinishAnalysisPipeline.html new file mode 100644 index 0000000..db060f5 --- /dev/null +++ b/R/vignettes/Vignette_06_FinishAnalysisPipeline.html @@ -0,0 +1,2335 @@ + + + + + + + + + + + + + + + +Vignette 06: Finish Analysis Pipeline + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +

+Vignette overview and learning objectives +

+

In this sixth and last vignette, you will finish using the simulated +detections of animal movements in the ABISSMAL data processing and +analysis workflow. You will detect clusters of detections that represent +distinct movement events, and then score behavioral inferences about the +movement events. You will also make plots to visualize these behavioral +inferences. You will continue to use coding skills that you learned in +the previous vignettes, and you will additionally learn how to build +more complex visualizations with ggplot.

+

+Load packages and your working directory path +

+
rm(list = ls()) # Clean global environment
+
+library(tidyverse) # Load the set of tidyverse packages
+library(data.table) # Load other packages that the ABISSMAL functions require
+
+# Initialize an object with the path that is your working directory
+path <- "/home/gsvidaurre/Desktop/ABISSMAL_vignettes"
+

+Load ABISSMAL functions +

+
# Load the function that detects clusters in the pre-processed data
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/detect_clusters.R")
+
+# Load the function that scores behavioral inferences about clusters
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/score_clusters.R")
+
+# Load a script with utility functions that each function above requires
+source("/home/gsvidaurre/Desktop/GitHub_repos/ABISSMAL/R/utilities.R")
+

+Finish the ABISSMAL pipeline +

+

Here you’ll use the ABISSMAL function detect_clusters() +to identify clusters of detections across sensor types (e.g. detections +from different sensors that were recorded close together in time).

+
# The run length needs to be set to 1 in order to correctly detect detection clusters of length 2
+detect_clusters(file_nms = c("pre_processed_data_RFID.csv", "pre_processed_data_IRBB.csv"), threshold = 2, run_length = 1, sensor_id_col_nm = "sensor_id", timestamps_col_nm = "timestamp_ms", PIT_tag_col_nm = "PIT_tag", rfid_label = "RFID", preproc_metadata_col_nms = c("thin_threshold_s", "data_stage", "date_pre_processed"), general_metadata_col_nms = c("chamber_id", "year", "month", "day"), path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "detection_clusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

Next, you’ll score behavioral inferences about these detection +clusters with the function score_clusters().

+
score_clusters(file_nm = "detection_clusters.csv", rfid_label = "RFID", camera_label = NULL, outer_irbb_label = "Outer Beam Breakers", inner_irbb_label = "Inner Beam Breakers", video_metadata_col_nms = NULL, integrate_perching = TRUE, perching_dataset = "RFID", perching_prefix = "perching_events_", sensor_id_col_nm = "sensor_id", PIT_tag_col_nm = "PIT_tag", pixel_col_nm = NULL, video_width = NULL, video_height = NULL, integrate_preproc_video = FALSE, video_file_nm = NULL, timestamps_col_nm = NULL, path = path, data_dir = "Data/processed", out_dir = "Data/processed", out_file_nm = "scored_detectionClusters.csv", tz = "America/New York", POSIXct_format = "%Y-%m-%d %H:%M:%OS")
+

+Check final results +

+

Now that you finished running the ABISSMAL pipeline to detect and +make inferences about movement events, you can check the final +results.

+
scored_clusters <- read.csv(file.path(path, "Data/processed", "scored_detectionClusters.csv")) %>% 
+  # The timestamps must be converted to POSIX format every time that the data is read back into R for plotting
+  dplyr::mutate(
+    start = as.POSIXct(format(as.POSIXct(start, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6")),
+    end = as.POSIXct(format(as.POSIXct(end, tz = "America/New York"), "%Y-%m-%d %H:%M:%OS6"))
+  ) %>%
+  # Arrange the rows by increasing timestamps
+  dplyr::arrange(-desc(start))
+
+glimpse(scored_clusters)
+
## Rows: 33
+## Columns: 20
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <chr> "exit", "entrance", "entrance", NA, NA, "entra…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:16:49.488812", "2024…
+

How many entrances and exits were scored per day?

+

+Missing data in R +

+

In order to count the number of each of these events scored per day, +you need to know how to account for missing data in R. Missing data is +often indicated using the value NA or “not available”, +which is a specific type of logical value in R. You can figure out +whether a vector (or a column in a data frame) contains missing values +by using the function is.na(), which will return TRUE when +it finds an NA (missing) value in the given vector.

+
?is.na()
+
+x <- c(1, NA, 2, 3, NA)
+
+is.na(x)
+
## [1] FALSE  TRUE FALSE FALSE  TRUE
+

In the code above, you created a vector called x that +has 2 NA values. The function is.na() checks whether each +element of x is equivalent to NA, and returns +TRUE when that condition is met (when it finds missing +data).

+

Because is.na() is a conditional statement, you can also +use other special symbols relevant to conditional statements, such as +the ! symbol, which will invert a conditional statement. +For instance, in the code below, by adding ! before +is.na(), you’re now asking whether each element of +x is not equivalent to NA:

+
!is.na(x)
+
## [1]  TRUE FALSE  TRUE  TRUE FALSE
+

As you can see, adding the ! in front of +is.na() results in an inverted output compared to +is.na() alone, such that each value that was previously +TRUE is now FALSE. Together, the ability to +invert the is.na() conditional statement, plus the binary +output that is.na() returns, is really useful for finding +and filtering rows of a data frame.

+

For instance, the function dplyr::filter() will drop a +row whenever it encounters a value of FALSE in a given +column, and will retain a row whenever it encounters a value of +TRUE. If you want to drop rows that contain NA +values for a given column, you would add +!is.na(name_of_column) inside of +dplyr::filter(), which should return FALSE +every time it encounters a row with NA, and will remove +that row during filtering.

+

+Count events per day +

+

Now you can write code to count the number of entrance and exit +events scored per day. You will need to 1) make a new column with +information about the day per timestamp, 2) drop rows with missing data +for the direction scored (because this information cannot be scored for +some movements), 3) group the data frame by day and direction scored, +and then 4) count the number of rows per group.

+
scored_clusters %>%
+  # Extract the day from each timestamp and make a new column with this information
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>% 
+  # Here you're using the function is.na(), which will return TRUE when it finds an NA (missing) value in the given column. By placing the ! before is.na(), you're inverting the output, so that all TRUE values are converted to FALSE. As a result, dplyr::filter will drop all rows that return the value FALSE (e.g. all rows with missing values in the column direction_scored)
+  dplyr::filter(!is.na(direction_scored)) %>%
+  # Group the data frame by both columns for which you want to count rows (events). Here you want to count the number of entrances and exits (categories in the column direction_scored) per day (categories in the column day)
+  group_by(day, direction_scored) %>% 
+  # Then summarize the data: the number of rows here is the number of exits or entrances scored per day
+  dplyr::summarise(
+    n = n()
+  )
+
## `summarise()` has grouped output by 'day'. You can override using the `.groups`
+## argument.
+
## # A tibble: 6 × 3
+## # Groups:   day [3]
+##     day direction_scored     n
+##   <int> <chr>            <int>
+## 1     1 entrance             4
+## 2     1 exit                 4
+## 3     2 entrance             4
+## 4     2 exit                 4
+## 5     3 entrance             4
+## 6     3 exit                 4
+

Four entrance and exit events were scored per day. Does this line up +with the anticipated number of exits and entrances per day? If you go +back to the code where you created the simulated raw datasets in +vignettes 03 and 04, you should see that you started by simulating 2 +entrances and exits per day across the RFID and beam breaker datasets. +Then, you added 2 more entrance and 2 more exit events per day when you +simulated RFID detection failures (e.g. movements captured by the beam +breakers only). score_clusters() detected the correct +number of entrances and exits per day.

+

Next, check out the perching events. Start by filtering out all rows +that were not scored as perching events (rows that have NA values in the +column “perching_PIT_tag”). Then, select only the columns that have +information that is useful to check: the PIT tag codes, as well as the +start and end of each perching event.

+
scored_clusters %>% 
+  # Use a conditional statement with is.na() to retain only rows that have PIT tag codes associated with perching events
+  dplyr::filter(!is.na(perching_PIT_tag)) %>%
+  # Then select only the columns that you want to check visually
+  dplyr::select(start, end, perching_PIT_tag)
+
##                 start                 end perching_PIT_tag
+## 1 2023-08-01 08:00:00 2023-08-01 08:00:02       1357aabbcc
+## 2 2023-08-01 11:30:00 2023-08-01 11:30:04       2468zzyyxx
+## 3 2023-08-02 08:00:00 2023-08-02 08:00:02       1357aabbcc
+## 4 2023-08-02 11:30:00 2023-08-02 11:30:04       2468zzyyxx
+## 5 2023-08-03 08:00:00 2023-08-03 08:00:02       1357aabbcc
+## 6 2023-08-03 11:30:00 2023-08-03 11:30:04       2468zzyyxx
+

As specified in vignettes 03 and 04, the first perching event each +day was performed by the first individual (PIT tag “1357aabbcc”), and +the second was attributed to the second individual (PIT tag +“2468zzyyxx”).

+

How many movement events that were not perching events were +attributed to each individual?

+
scored_clusters %>%
+  # Extract the day from each timestamp and make a new column with this information
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>%
+  # Use a conditional statement with is.na() to retain only rows that have PIT tag codes not associated with perching events
+  # Here you're combining two conditional statements, which allows you to search for rows in the column individual_initiated that have PIT tag codes, but that also do not have a sensor label in the column perching_sensor (e.g. movement events that were not perching events)
+  dplyr::filter(!is.na(individual_initiated) & is.na(perching_sensor)) %>%
+  group_by(day, individual_initiated) %>% 
+  dplyr::summarise(
+    n = n()
+  )
+
## `summarise()` has grouped output by 'day'. You can override using the `.groups`
+## argument.
+
## # A tibble: 3 × 3
+## # Groups:   day [3]
+##     day individual_initiated     n
+##   <int> <chr>                <int>
+## 1     1 1357aabbcc               4
+## 2     2 1357aabbcc               4
+## 3     3 1357aabbcc               4
+

As you can see, 4 movement events that were not perching events were +attributed to the first individual (PIT tag code 1357aabbcc) on each +day. This is exactly what we expected when creating the simulated +dataset (see vignettes 03 and 04). More non-perching movement events +were detected across these days, but those events were not always +captured by the RFID antenna (e.g. the simulated RFID detection failures +that were captured by the beam breakers only). The beam breakers +captured those movement events but cannot record individual +identity.

+

+Build complex barcode plot +

+

Now that you checked out the final results, you can visualize them. +In the code below, you’ll learn how to build a barcode plot that is more +complex but also easier to interpret than the plot that you made in +vignette 05. For this plot, it would be helpful to visualize 3 types of +behavioral inferences or types of information over time: the direction +of movement (when available), the identity of the individual (when +available), and perching events.

+

You can start building the plot by adding vertical line segments for +non-perching movement events. The color of each line will indicate the +individual identity, or whether individual identity could not be +assigned. The line type of each line will indicate the direction of +movement, or if direction could not be scored.

+

In order to encode colors and line types correctly in the plot, you +need to modify the data frame of final results to convert NAs in the two +associated columns to more meaningful information. For instance, when +individual identity is missing, it would be useful to convert the +associated NA value to “unassigned”. In the code below, you will use the +function is.na() inside of ifelse() +conditional statements to change the NA values inside the columns +“individual_initiated” and “direction_scored” to more useful information +for plotting purposes.

+

First you can practice using is.na() inside of +ifelse() to create a new vector. In the code below, you’re +providing the conditional statement that you want to test (here you’re +testing whether the column holding the PIT tag code of the individual +that initiated a movement has NAs), the value you want to add to the +vector if the condition is true (“unassigned” when no PIT tag code is +present), and then the value that you want to add if the condition is +false (return the PIT tag code in the individual_initiated column if the +given value is not NA).

+
# is.na() returns TRUE when it encounters NAs inside of a vector (or column), and FALSE when a value inside of the vector is not NA
+is.na(scored_clusters$individual_initiated)
+
##  [1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE
+## [13]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE
+## [25]  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
+
# All NAs in this column are converted to "unassigned", but all other values were not changed
+ifelse(test = is.na(scored_clusters$individual_initiated), yes = "unassigned", no = scored_clusters$individual_initiated)
+
##  [1] "unassigned" "unassigned" "unassigned" "1357aabbcc" "unassigned"
+##  [6] "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc" "unassigned"
+## [11] "2468zzyyxx" "unassigned" "unassigned" "unassigned" "1357aabbcc"
+## [16] "unassigned" "1357aabbcc" "1357aabbcc" "1357aabbcc" "1357aabbcc"
+## [21] "unassigned" "2468zzyyxx" "unassigned" "unassigned" "unassigned"
+## [26] "1357aabbcc" "unassigned" "1357aabbcc" "1357aabbcc" "1357aabbcc"
+## [31] "1357aabbcc" "unassigned" "2468zzyyxx"
+

Now you can use is.na() inside of ifelse +statements to modify columns in the data frame.

+
scored_clusters_gg <- scored_clusters %>% 
+  dplyr::mutate(
+    # If this column has an NA value, then convert it to "unassigned"
+    # Else, do not change the given value
+    individual_initiated = ifelse(is.na(individual_initiated), "unassigned", individual_initiated),
+    # Repeat this process for the direction_scored column but with a different value
+    # Also, in the conditional statement below, you added is.na(perching_sensor) (after the & symbol) to only convert NA values in direction_scored when they were also not labeled as perching events in the column perching_sensor
+    direction_scored = ifelse(is.na(direction_scored) & is.na(perching_sensor), "not scored", direction_scored)
+  ) %>% 
+  # Next, you'll convert each of these columns to type factor and arrange the levels in order for plotting (for example, the valures "unassigned" and "not scored" should come last in the legend)
+  dplyr::mutate(
+    individual_initiated = factor(individual_initiated, levels = c("1357aabbcc", "2468zzyyxx", "unassigned")),
+    direction_scored = factor(direction_scored, levels = c("entrance", "exit", "not scored"))
+  )
+
+# Check the resulting changes using the function distinct() to see all of the unique values for both columns that were modified
+# The NA values in the direction_scored column are expected since these refer to perching events
+scored_clusters_gg %>% 
+  distinct(individual_initiated, direction_scored)
+
##   individual_initiated direction_scored
+## 1           unassigned             exit
+## 2           unassigned         entrance
+## 3           1357aabbcc             <NA>
+## 4           unassigned       not scored
+## 5           1357aabbcc         entrance
+## 6           1357aabbcc             exit
+## 7           2468zzyyxx             <NA>
+

This output looks good. You should see NA values in this +data frame, but they’re in the column “direction_scored” and associated +with the eprching events, which you’ll be adding to the plot in a +separate layer of code later on.

+

You can use this modified data frame to build the plot. In the code +below, you’ll add lines colored by individual identity and with line +types encoding the direction of movement. First you’ll specify the plot +aesthetics:

+
# Colors will be encoded in the same order as the levels of the column individual_initiated, so orange will encode "1357aabbcc"
+levels(scored_clusters_gg$individual_initiated)
+
## [1] "1357aabbcc" "2468zzyyxx" "unassigned"
+
cols <- c("orange", "darkgreen", "black")
+
+# Line types will be encoded in the same order as the levels of the column direction_scored, so dotted will encode "not scored"
+levels(scored_clusters_gg$direction_scored)
+
## [1] "entrance"   "exit"       "not scored"
+
ltys <- c("solid", "longdash", "dotted")
+

Then you can add lines to the plot by individual identity. Here, +you’re splitting up the geom_segment() calls by unique +values in the individual_initiated column. You only added segments for +the first individual and all non-perching events that were not assigned +to an individual, because from checking the results above, you know that +no non-perching events were assigned to the second individual.

+
ggplot() +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  )
+

+

To make this plot, you vertically offset the line segments for the +first individual and those that were unassigned. This vertical offset +makes it easier to visually compare patterns over time. You created this +vertical offset by changing the values used for y and +yend in the second geom_segment() layer, so +that those line segments would start higher than the top of the first +set of segments.

+

You can make some additional modifications that would help make this +plot easier to interpret. First, the panel or facet labels on the left +could be changed from the date to the day of data collection (e.g. “Day +1”). The x-axis labels could also be changed to show only the time of +day (remove the month and day), and there could also be more labels +available (e.g. a label each half hour).

+

You can start by modifying the data frame to add information about +the day of data collection. You’ll create this new column by relying on +conditional statements with the ifelse() function, since +there are only 3 days of data collection that need recoding.

+
# Create a new column in the raw data for the date of data collection
+scored_clusters_gg2 <- scored_clusters_gg %>% 
+  # First you need to create a column with information about the day
+  dplyr::mutate(
+    day = lubridate::day(start)
+  ) %>% 
+  dplyr::mutate(
+    # Then recode the label for each day and save this in a new column
+    day_label = ifelse(day == 1, "Day 1", day), # Here the last argument is day because the column day_label does not exist yet
+    day_label = ifelse(day == 2, "Day 2", day_label),
+    day_label = ifelse(day == 3, "Day 3", day_label)
+  )
+
+# Looks good  
+glimpse(scored_clusters_gg2)
+
## Rows: 33
+## Columns: 22
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <fct> exit, entrance, entrance, NA, not scored, entr…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <fct> unassigned, unassigned, unassigned, 1357aabbcc…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:16:49.488812", "2024…
+## $ day                     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
+## $ day_label               <chr> "Day 1", "Day 1", "Day 1", "Day 1", "Day 1", "…
+
scored_clusters_gg2 %>% 
+  distinct(day_label)
+
##   day_label
+## 1     Day 1
+## 2     Day 2
+## 3     Day 3
+

Now you can update the code to include the new date labels:

+
# Add the data frame as the default dataset for the base layer plot, so that the facet_wrap() layer below has data to plot
+ggplot(data = scored_clusters_gg2) +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg2 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start, y = 0, xend = end, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg2 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start, y = 0.6, xend = end, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  ) +
+  
+  # Facet the plot by day (e.g. create a panel per day)
+  # Use the new day labels here
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left")
+

+

Now that information about the day of data collection has been moved +to the facet labels, you need to fix the x-axis labels. The plot will be +more interpretable if you can line up the timestamps by hour and minute +for a direct comparison across days.

+

To line up the timestamps across days, you will need to update the +format of the columns that hold timestamps. The code to convert the +timestamps to a different format is nested and repetitive, but the +timestamp conversion will be performed correctly. When you prompt the R +to convert the timestamps to hours, minutes, and seconds only, R adds a +default year, month, and day beforehand (likely the current date that +you rant the code). This is expected, and it is not an error, but rather +makes it possible for the timestamps to align correctly over days in the +plot (since R sees all timestamps occurring on a single day).

+
scored_clusters_gg3 <- scored_clusters_gg2 %>% 
+  dplyr::mutate(
+    start_gg = as.POSIXct(strptime(format(as.POSIXct(start), "%H:%M:%S"), format = "%H:%M:%S")),
+    end_gg = as.POSIXct(strptime(format(as.POSIXct(end), "%H:%M:%S"), format = "%H:%M:%S"))
+  )
+  
+# You'll see that a new year, month, and day were appended to the updated timestamps, but this is expected (see above)
+glimpse(scored_clusters_gg3)
+
## Rows: 33
+## Columns: 24
+## $ start                   <dttm> 2023-08-01 06:05:04, 2023-08-01 06:35:08, 202…
+## $ end                     <dttm> 2023-08-01 06:05:05, 2023-08-01 06:35:09, 202…
+## $ sensor_ids              <chr> "Inner Beam Breakers; Outer Beam Breakers", "O…
+## $ Edge_1                  <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ Edge_2                  <chr> NA, NA, NA, NA, NA, "RFID - Inner Beam Breaker…
+## $ direction_scored        <fct> exit, entrance, entrance, NA, not scored, entr…
+## $ direction_rule          <chr> "Inner Beam Breakers - Outer Beam Breakers", "…
+## $ indiv1_id               <chr> "1357aabbcc", "1357aabbcc", "1357aabbcc", "135…
+## $ indiv2_id               <chr> "2468zzyyxx", "2468zzyyxx", "2468zzyyxx", "246…
+## $ total_indiv1_detections <int> NA, NA, NA, 2, NA, 1, 1, 1, 1, NA, 0, NA, NA, …
+## $ total_indiv2_detections <int> NA, NA, NA, 0, NA, 0, 0, 0, 0, NA, 3, NA, NA, …
+## $ individual_initiated    <fct> unassigned, unassigned, unassigned, 1357aabbcc…
+## $ individual_ended        <chr> NA, NA, NA, "1357aabbcc", NA, "1357aabbcc", "1…
+## $ perching_sensor         <chr> NA, NA, NA, "RFID", NA, NA, NA, NA, NA, NA, "R…
+## $ perching_PIT_tag        <chr> NA, NA, NA, "1357aabbcc", NA, NA, NA, NA, NA, …
+## $ perching_start          <chr> NA, NA, NA, "2023-08-01 08:00:00", NA, NA, NA,…
+## $ perching_end            <chr> NA, NA, NA, "2023-08-01 08:00:03", NA, NA, NA,…
+## $ perching_duration_s     <int> NA, NA, NA, 3, NA, NA, NA, NA, NA, NA, 5, NA, …
+## $ data_stage              <chr> "integrated", "integrated", "integrated", "int…
+## $ date_processed          <chr> "2024-04-07 2024-04-07 19:16:49.488812", "2024…
+## $ day                     <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2…
+## $ day_label               <chr> "Day 1", "Day 1", "Day 1", "Day 1", "Day 1", "…
+## $ start_gg                <dttm> 2024-04-07 06:05:04, 2024-04-07 06:35:08, 202…
+## $ end_gg                  <dttm> 2024-04-07 06:05:05, 2024-04-07 06:35:09, 202…
+

Now you can update the plotting code to change the aesthetics of the +x-axis using the function scale_x_datetime() to specify +that you want x-axis labels every half hour. Also add an x-axis title, +and remove the y-axis grid lines inside of each panel:

+
gg <- ggplot(data = scored_clusters_gg3) +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top"
+  ) +
+  
+  # Facet the plot by day (e.g. create a panel per day)
+  # Use the new day labels here
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left") +
+  
+  # Change the aesthetics of the x-axis text
+  scale_x_datetime(
+    date_breaks = "30 mins",
+    date_labels = "%H:%M"
+  ) +
+  
+  # Add an x-axis title
+  xlab("Time of day (HH:MM)") +
+  
+  # Remove the y-axis grid lines (major and minor) inside of each panel
+  theme(
+    panel.grid.major.y = element_blank(),
+    panel.grid.minor.y = element_blank()
+  ) 
+
+gg
+

+

There’s still one more important piece of information to add to the +plot: the perching events. You’ll add these using +geom_segment(), but you’ll make these line segments short +and thick, so that they appear more like dots on the plot:

+
gg <- gg + 
+  
+  # Add the perching events as rounded segments, and now encode color through the column individual_initiated
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(!is.na(perching_sensor)),
+    aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated),
+    linewidth = 2, lineend = "round"
+  ) +
+  
+  # Add the custom colors. These colors also apply to the lines that represent non-perching events that you added in previous geom_segement() layers
+  scale_color_manual(values = cols)
+
+gg
+

+

You should see that a color legend appears at the top of the plot +with the addition of the new geom_segment() layer. Both +legends are a little messy. You can improve the plot by updating the +title of each legend, increasing the size of the legend text, and +reducing the white space between the plot and the legends. To modify the +legend titles, you’ll rely on the functions guides() and +guide_legend(). To increase the legend text size, you’ll +use the argument legend.text inside of the function +theme(). To reduce white space between the plot and +legends, you’ll use the argument legend.margin inside of +the function theme().

+
# Check out more information about the function used to control margins (white space) around the legend
+?margin
+
+gg <- gg +
+  
+  # Increase the legend text size and reduce white space between the plot and legends
+  theme(
+    legend.text = element_text(size = 10),
+    legend.margin = margin(-1, -1, -1, -1, unit = "pt")
+  ) +
+
+  # Change the legend titles
+  guides(
+    linetype = guide_legend(title = "Direction"),
+    color = guide_legend(title = "Individual")
+  )
+
+gg
+

+

Finally, you can save this plot as an image file:

+
gg
+

+
# Save the image file to your computer
+ggsave(file.path(path, "behavioral_inferences.tiff"), width = 8, height = 6, units = "in", dpi = 300)
+

You can continue to modify minor aesthetics to this image file to +create a high-quality figure for a publication. For instance, you can +change the final size of the image file (width, +height), as well as the resolution (dpi). You +can also change the size of the text on each axis and the axis titles, +or the legend position, as you play around with the final image +size.

+

This is the all of the code used for the final plot, condensed and +reorganized:

+
ggplot(data = scored_clusters_gg3) +
+  
+  # Add a vertical line for each non-perching event assigned to the first individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "1357aabbcc"),
+    aes(x = start_gg, y = 0, xend = end_gg, yend = 0.5, linetype = direction_scored),
+    color = "orange",
+    linewidth = 0.5
+  ) +
+  
+  # Add a vertical line for each non-perching event that was not assigned to either individual
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(individual_initiated == "unassigned"),
+    aes(x = start_gg, y = 0.6, xend = end_gg, yend = 1.1, linetype = direction_scored),
+    color = "black",
+    linewidth = 0.5
+  ) +
+  
+  # Add the perching events as rounded segments, and now encode color through the column individual_initiated
+  geom_segment(
+    data = scored_clusters_gg3 %>% 
+      dplyr::filter(!is.na(perching_sensor)),
+    aes(x = start_gg, y = 1.2, xend = end_gg, yend = 1.2, color = individual_initiated),
+    linewidth = 2, lineend = "round"
+  ) +
+  
+  # Add the custom linetype values to this plot
+  scale_linetype_manual(values = ltys) +
+  
+  # Add the custom colors
+  scale_color_manual(values = cols) +
+  
+  # Change the legend titles
+  guides(
+    linetype = guide_legend(title = "Direction"),
+    color = guide_legend(title = "Individual")
+  ) +
+  
+  # Add an x-axis title
+  xlab("Time of day (HH:MM)") +
+  
+  # Remove the y-axis title
+  ylab("") +
+  
+  # Change the aesthetics of the x-axis text
+  scale_x_datetime(
+    date_breaks = "30 mins",
+    date_labels = "%H:%M"
+  ) +
+  
+  # Facet the plot by day (e.g. create a panel per day)
+  # Use the new day labels here
+  facet_wrap(~ day_label, nrow = 3, strip.position = "left") +
+  
+  # Use this function to convert the plot background to black and white
+  theme_bw() +
+  
+  # Use aesthetics functions to remove the y-axis text and ticks
+  # Add an argument to change where the legend is located in the plot
+  # Remove the y-axis grid lines (major and minor) inside of each panel
+  # Increase the legend text size and reduce white space between the plot and legend
+  theme(
+    axis.text.y = element_blank(),
+    axis.ticks.y = element_blank(),
+    legend.position = "top",
+    panel.grid.major.y = element_blank(),
+    panel.grid.minor.y = element_blank(),
+    legend.text = element_text(size = 10),
+    legend.margin = margin(-1, -1, -1, -1, unit = "pt")
+  )
+

You completed the final vignette of the ABISSMAL data processing and +analysis pipeline. You also practiced your coding and data science +skills in a biological context. Well done! It would be very helpful if +you could complete the brief Google form for a post-vignette evaluation +that will help us continue to improve these vignettes over time. A link +to this Google form will be available in the README file for the +vignettes.

+ + + +
+
+ +
+ + + + + + + + + + + + + + + + diff --git a/R/vignettes/images/ABISSMAL_localrepo.png b/R/vignettes/images/ABISSMAL_localrepo.png new file mode 100644 index 0000000..6ad0fbc Binary files /dev/null and b/R/vignettes/images/ABISSMAL_localrepo.png differ diff --git a/R/vignettes/images/Figure4_ComputationalAnalyses.png b/R/vignettes/images/Figure4_ComputationalAnalyses.png new file mode 100644 index 0000000..f5795c8 Binary files /dev/null and b/R/vignettes/images/Figure4_ComputationalAnalyses.png differ diff --git a/R/vignettes/images/RStudio_01.png b/R/vignettes/images/RStudio_01.png new file mode 100644 index 0000000..727ad8f Binary files /dev/null and b/R/vignettes/images/RStudio_01.png differ diff --git a/R/vignettes/images/RStudio_02.png b/R/vignettes/images/RStudio_02.png new file mode 100644 index 0000000..5be5084 Binary files /dev/null and b/R/vignettes/images/RStudio_02.png differ diff --git a/R/vignettes/styles.css b/R/vignettes/styles.css new file mode 100644 index 0000000..b71f3d9 --- /dev/null +++ b/R/vignettes/styles.css @@ -0,0 +1,83 @@ + +/* Header 1 */ +h1 { + + font-size: 25px; + font-weight: bold; + +} + +/* Header 2 */ +h2 { + + font-size: 24px; + font-weight: bold; + +} + +/* Header 3 */ +h2 { + + font-size: 22px; + font-weight: bold; + +} + +/* Header 4 */ +h3 { + + font-size: 20px; + font-weight: bold; + +} + +/* Body text */ +body { + + font-size: 16px; + +} + +/* Code inside of chunks (including output) */ +pre code { + + font-size: 16px; + /* color: #708090; /* + /* background-color: #F8F8FF; */ + +} + +/* Code inside of text (wrapped in ``)*/ +code { + + font-size: 16px; + +} + +/* Unordered list */ +ul { + + font-size: 16px; + +} + +/* Ordered list */ +ol { + + font-size: 16px; + +} + +/* class "testing" for code chunks */ +.watch-out { + background-color: lightpink; + border: 3px solid red; + font-weight: bold; +} + +/* class "necessary" for code chunks */ +.code-necessary { + background-color: lightpink; + border: 3px solid red; + font-weight: bold; +}