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 @@ +
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”.
+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:
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.
+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:
+
+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í:
+
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.
+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”:
+
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.
+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:
+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
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
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
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
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.
+* 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.
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).
+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.
+ + + +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”.
+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:
+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:
+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.
+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()
).
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.
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)
+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
+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.
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.
+ + + +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”.
+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:
+dataframes
pipe
en el tidyverse
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
+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.
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.
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
+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,…
+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.
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”.
+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:
+dataframes
dataframes
con R base y el
+tidyverse
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
+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" ...
+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…
+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
+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
+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
+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$")
+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)
+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.
+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)
+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)
+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.
+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.
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”.
+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:
ggplot
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
+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")
+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.
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"
+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.
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…
+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.
+ + + +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”.
+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()
.
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
+# 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")
+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")
+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?
+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.
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.
+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.
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:
+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.
+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):
+
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:
+
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.
+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:
+
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 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:
+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
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)
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)
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
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 from Smith-Vidaurre, G., Molina, T., Jarvis, E.D., and E.A.
+Hobson. 2023. Automated tracking of avian parental care. EcoEvoRxiv
+preprint.
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).
+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.
+ + + +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:
+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:
+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.
+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()
).
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.
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)
+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
+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.
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.
+ + + +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:
+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
+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.
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.
+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
+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,…
+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.
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:
+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
+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" ...
+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…
+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
+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
+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
+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$")
+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)
+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.
+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)
+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)
+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.
+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.
+ + + +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:
+ggplot
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"
+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")
+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.
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"
+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).
+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…
+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.
+ + + +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.
+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")
+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")
+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?
+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.
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.
+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.
+ + + +